import { Z80RegistersClass , Z80Registers } from '../remotes/z80registers' ; import { StepHistoryClass } from './stephistory' ; import { HistoryInstructionInfo , DecodeHistoryInfo } from './decodehistinfo' ; import { RefList } from '../misc/reflist' ; import { CallStackFrame } from '../callstackframe' ; import { Remote } from '../remotes/remotebase' ; import { Labels } from '../labels/labels' ; import { Utility } from '../misc/utility' ; import { TimeWait } from '../misc/timewait' ; /**
 * Use similar data as DecodeRegisterData but with data extension.
 * This extension is read and parsed as well.
 * To get the opcodes at the pc and the contents at (SP).
 * Also returns the extended (long) PC address, i.e. the bank (+1) the PC is used in.
 * This is required only for true cpu history (not for lite/step history).
 */ export class DecodeStandardHistoryInfo extends DecodeHistoryInfo { param line return public getOpcodes ( line ) { const data = line as Uint16Array ; const index = data . length - 3 ; let opcodes = data [ index ] ; opcodes |= data [ index + 1 ] << 16 ; return opcodes ; } param line returns public getSPContent ( line ) { const data = line as Uint16Array ; const index = data . length - 1 ; let spContents = data [ index ] ; return spContents ; } } /**
 * This class takes care of the cpu history.
 * It is base class for e.g. ZesaruxCpuHistory or used directly by the
 * ZxSimulatorRemote.
 */ export class CpuHistoryClass extends StepHistoryClass { protected reverseDbgStack : RefList < CallStackFrame > ; constructor ( ) { super ( ) ; } /**
     * Sets the static CpuHistory singleton.
     */ public static setCpuHistory ( cpuHistory ) { StepHistory = cpuHistory as any ; CpuHistory = undefined as any ; if ( cpuHistory instanceof CpuHistoryClass ) CpuHistory = cpuHistory ; } /**
     * Removes the CpuHistory singleton.
     */ public static removeCpuHistory ( ) { CpuHistoryClass . setCpuHistory ( undefined ) ; } /**
     * Init.
     */ public init ( ) { super . init ( ) ; this . reverseDbgStack = new RefList < CallStackFrame > ( ) ; } /**
     * Clears the history cache. Is called on each "normal (forward)" step.
     */ public clear ( ) { this . history . length = 0 ; this . historyIndex = - 1 ; this . revDbgHistory . length = 0 ; this . reverseDbgStack = undefined as any ; } /**
     * Not used in cpu history.
     */ public pushCallStack ( callstack ) { Utility . assert ( false ) ; } param index returns protected async getRemoteHistoryIndex ( index ) { Utility . assert ( false ) ; return undefined ; } /**
     * Returns the call stack.
     */ public getCallStack ( ) { return this . reverseDbgStack ; } /**
     * Emits 'historySpot' to signal that the files should be decorated.
     * It can happen that this method has to retrieve data from the
     * remote.
     */ /**
     * Retrieves the data for the history buffer for the history spot.
     * history buffer is cleared before.
     * Is not called in stepback mode.
     */ public async getHistorySpotFromRemote ( ) { Utility . assert ( ! this . isInStepBackMode ( ) ) ; let count = this . spotCount ; this . history . length = 0 ; for ( let i = 0 ; i < count ; i ++ ) { const line = await this . getRemoteHistoryIndex ( i ) ; if ( ! line ) break ; this . history . push ( line ) ; } } returns public async getPrevRegistersAsync ( ) { let count = this . spotCount ; if ( count < 1 ) count = 1 ; const index = this . historyIndex + 1 ; if ( index >= this . maxSize ) return undefined ; let endIndex = index + count ; if ( endIndex > this . maxSize ) endIndex = this . maxSize ; for ( let i = this . history . length ; i < endIndex ; i ++ ) { const line = await this . getRemoteHistoryIndex ( i ) ; if ( ! line ) break ; this . history . push ( line ) ; } if ( index >= this . history . length ) return undefined ; const currentLine = this . history [ index ] ; this . historyIndex = index ; return currentLine ; } param opcodes param flags returns public isRetAndExecuted ( opcodes , flags ) { const opcode0 = opcodes & 0xFF ; if ( 0xC9 == opcode0 ) return true ; if ( 0xED == opcode0 ) { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( 0x4D == opcode1 || 0x45 == opcode1 ) return true ; } const mask = 0b11000111 ; if ( ( opcode0 & mask ) == 0b11000000 ) { const cc = ( opcode0 & ~ mask ) >>> 3 ; const condMet = Z80RegistersClass . isCcMetByFlag ( cc , flags ) ; return condMet ; } return false ; } param opcodes param flags returns public isCallAndExecuted ( opcodes , flags ) { const opcode0 = opcodes & 0xFF ; if ( 0xCD == opcode0 ) return true ; const mask = 0b11000111 ; if ( ( opcode0 & mask ) == 0b11000100 ) { const cc = ( opcode0 & ~ mask ) >>> 3 ; const condMet = Z80RegistersClass . isCcMetByFlag ( cc , flags ) ; return condMet ; } return false ; } param opcodes returns public isRst ( opcodes ) { const opcode0 = opcodes & 0xFF ; return this . isRstOpcode ( opcode0 ) ; } param opcodes returns public getRstAddress ( opcodes ) { const opcode0 = opcodes & 0xFF ; const mask = ~ 0b11000111 ; const address = ( opcode0 & mask ) ; return address ; } param opcodes returns param opcodes returns public isPop ( opcodes ) { const opcode0 = opcodes & 0xFF ; const mask = 0b11001111 ; if ( ( opcode0 & mask ) == 0b11000001 ) return true ; if ( opcode0 == 0xDD || opcode0 == 0xFD ) { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( opcode1 == 0xE1 ) return true ; } return false ; } param opcodes param line returns public getPushedValue ( opcodes , line ) { const opcode0 = opcodes & 0xFF ; let value ; switch ( opcode0 ) { case 0xC5 : value = Z80Registers . decoder . parseBC ( line ) ; break ; case 0xD5 : value = Z80Registers . decoder . parseDE ( line ) ; break ; case 0xE5 : value = Z80Registers . decoder . parseHL ( line ) ; break ; case 0xF5 : value = Z80Registers . decoder . parseAF ( line ) ; break ; case 0xDD : case 0xFD : { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( opcode1 == 0xE5 ) { if ( opcode0 == 0xDD ) value = Z80Registers . decoder . parseIX ( line ) ; else value = Z80Registers . decoder . parseIY ( line ) ; } } break ; case 0xED : { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( opcode1 == 0x8A ) { value = ( opcodes >>> 8 ) & 0xFF00 ; value |= ( opcodes >>> 24 ) & 0xFF ; } } break ; } return value ; } param opcodes param sp param line return public calcDirectSpChanges ( opcodes , sp , line ) { let expectedSp = sp ; const opcode0 = opcodes & 0xFF ; switch ( opcode0 ) { case 0x31 : expectedSp = ( opcodes >>> 8 ) & 0xFFFF ; break ; case 0x33 : expectedSp ++ ; break ; case 0x3B : expectedSp -- ; break ; case 0xF9 : const hl = Z80Registers . decoder . parseHL ( line ) ; expectedSp = hl ; break ; case 0xED : { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( opcode1 == 0x7B ) { expectedSp = undefined ; } } break ; case 0xDD : { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( opcode1 == 0xF9 ) { const ix = Z80Registers . decoder . parseIX ( line ) ; expectedSp = ix ; } } break ; case 0xFD : { const opcode1 = ( opcodes >>> 8 ) & 0xFF ; if ( opcode1 == 0xF9 ) { const iy = Z80Registers . decoder . parseIY ( line ) ; expectedSp = iy ; } } break ; } return expectedSp ; } param opcode0 returns public isCallOpcode ( opcode0 ) { if ( 0xCD == opcode0 ) return true ; const mask = 0b11000111 ; if ( ( opcode0 & mask ) == 0b11000100 ) return true ; return false ; } param opcode0 returns public isRstOpcode ( opcode0 ) { const mask = 0b11000111 ; if ( ( opcode0 & mask ) == 0b11000111 ) return true ; return false ; } /**
     * Returns the pointer to the virtual reverse debug stack.
     * If it does not exist yet it will be created and prefilled with the current
     * (memory) stack values.
     */ protected async prepareReverseDbgStack ( ) { if ( ! this . isInStepBackMode ( ) ) { this . reverseDbgStack = await Remote . getCallStackCache ( ) ; } } param currentLine param prevLine protected async handleReverseDebugStackBack ( currentLine , prevLine ) { Utility . assert ( currentLine ) ; let sp = Z80Registers . decoder . parseSP ( currentLine ) ; const opcodes = this . decoder . getOpcodes ( currentLine ) ; const flags = Z80Registers . decoder . parseAF ( currentLine ) ; let frame = this . reverseDbgStack . last ( ) ; if ( ! frame ) { frame = new CallStackFrame ( 0 , sp , Remote . getMainName ( sp ) ) ; this . reverseDbgStack . push ( frame ) ; } if ( this . isRetAndExecuted ( opcodes , flags ) ) { const retAddr = this . decoder . getSPContent ( currentLine ) ; const data = await Remote . readMemoryDump ( ( retAddr - 3 ) & 0xFFFF , 3 ) ; const firstByte = data [ 0 ] ; let callAddr ; if ( this . isCallOpcode ( firstByte ) ) { const lowByte = data [ 1 ] ; const highByte = data [ 2 ] ; callAddr = ( highByte << 8 ) + lowByte ; } else if ( this . isRstOpcode ( firstByte ) ) { callAddr = firstByte & 0b00111000 ; } let labelCallAddr ; if ( callAddr == undefined ) { labelCallAddr = "__UNKNOWN__" ; } else { const labelCallAddrArr = Labels . getLabelsForNumber64k ( callAddr ) ; labelCallAddr = ( labelCallAddrArr . length > 0 ) ? labelCallAddrArr [ 0 ] : Utility . getHexString ( callAddr , 4 ) + 'h' ; } const expectedPrevSP = sp + 2 ; const prevSP = Z80Registers . decoder . parseSP ( prevLine ) ; if ( expectedPrevSP != prevSP ) { this . reverseDbgStack . pop ( ) ; } const pc = Z80Registers . decoder . parsePCLong ( currentLine ) ; const frame = new CallStackFrame ( pc , sp , labelCallAddr ) ; this . reverseDbgStack . push ( frame ) ; return ; } let pushedValue ; if ( this . isPop ( opcodes ) ) { pushedValue = this . decoder . getSPContent ( currentLine ) ; sp += 2 ; } const spPrev = Z80Registers . decoder . parseSP ( prevLine ) ; let count = sp - spPrev ; if ( count > 0 ) { while ( count > 1 && this . reverseDbgStack . length > 0 ) { while ( count > 1 && frame . stack . length > 0 ) { frame . stack . pop ( ) ; count -= 2 ; } if ( count > 1 ) { if ( this . reverseDbgStack . length <= 1 ) break ; this . reverseDbgStack . pop ( ) ; count -= 2 ; frame = this . reverseDbgStack . last ( ) ; } } } else { while ( count < - 1 ) { frame . stack . push ( undefined ) ; count += 2 ; } } if ( ! frame ) { frame = new CallStackFrame ( 0 , sp , Remote . getMainName ( sp ) ) ; this . reverseDbgStack . push ( frame ) ; } const pc = Z80Registers . decoder . parsePCLong ( currentLine ) ; frame . addr = pc ; if ( pushedValue != undefined ) frame . stack . push ( pushedValue ) ; } returns public async stepBack ( ) { await this . prepareReverseDbgStack ( ) ; let breakReasonString ; try { let prevLine = Z80Registers . getCache ( ) ; Utility . assert ( prevLine ) ; const currentLine = await this . revDbgPrev ( ) ; if ( currentLine ) { await this . handleReverseDebugStackBack ( currentLine , prevLine ) ; } else breakReasonString = 'Break: Reached end of instruction history.' ; } catch ( e ) { breakReasonString = e . message ; } return breakReasonString ; } param currentLine param nextLine protected handleReverseDebugStackForward ( currentLine , nextLine ) { Utility . assert ( currentLine ) ; Utility . assert ( nextLine ) ; let sp = Z80Registers . decoder . parseSP ( currentLine ) ; let expectedSP = sp ; let expectedPC ; const opcodes = this . decoder . getOpcodes ( currentLine ) ; const flags = Z80Registers . decoder . parseAF ( currentLine ) ; const nextSP = Z80Registers . decoder . parseSP ( nextLine ) ; let frame = this . reverseDbgStack . last ( ) ; if ( ! frame ) { frame = new CallStackFrame ( 0 , sp , Remote . getMainName ( sp ) ) ; this . reverseDbgStack . push ( frame ) ; } if ( this . isCallAndExecuted ( opcodes , flags ) ) { sp -= 2 ; expectedSP = sp ; const callAddr = ( opcodes >>> 8 ) & 0xFFFF ; const labelCallAddrArr = Labels . getLabelsForNumber64k ( callAddr ) ; const labelCallAddr = ( labelCallAddrArr . length > 0 ) ? labelCallAddrArr [ 0 ] : Utility . getHexString ( callAddr , 4 ) + 'h' ; const name = labelCallAddr ; frame = new CallStackFrame ( 0 , nextSP - 2 , name ) ; this . reverseDbgStack . push ( frame ) ; } else if ( this . isRst ( opcodes ) ) { sp -= 2 ; expectedSP = sp ; const callAddr = this . getRstAddress ( opcodes ) ; const labelCallAddrArr = Labels . getLabelsForNumber64k ( callAddr ) ; const labelCallAddr = ( labelCallAddrArr . length > 0 ) ? labelCallAddrArr [ 0 ] : Utility . getHexString ( callAddr , 4 ) + 'h' ; const name = labelCallAddr ; frame = new CallStackFrame ( 0 , nextSP - 2 , name ) ; this . reverseDbgStack . push ( frame ) ; } else { const pushedValue = this . getPushedValue ( opcodes , currentLine ) ; if ( pushedValue != undefined ) { frame . stack . unshift ( pushedValue ) ; sp -= 2 ; expectedSP = sp ; } else if ( this . isPop ( opcodes ) || this . isRetAndExecuted ( opcodes , flags ) ) { expectedSP += 2 ; } else { expectedSP = this . calcDirectSpChanges ( opcodes , sp , currentLine ) ; if ( expectedSP == undefined ) { const pc = Z80Registers . decoder . parsePC ( currentLine ) ; expectedPC = pc + 4 ; } } } let interruptFound = false ; const nextPC = Z80Registers . decoder . parsePCLong ( nextLine ) ; if ( expectedSP != undefined ) { if ( nextSP == expectedSP - 2 ) interruptFound = true ; } else { Utility . assert ( expectedPC ) ; if ( nextPC != expectedPC ) interruptFound = true ; } let usedSP = expectedSP ; if ( ! usedSP ) usedSP = Z80Registers . decoder . parseSP ( nextLine ) ; let count = usedSP - sp ; if ( count > 0 ) { while ( count > 1 && this . reverseDbgStack . length > 0 ) { while ( count > 1 && frame . stack . length > 0 ) { frame . stack . pop ( ) ; count -= 2 ; } if ( count > 1 ) { this . reverseDbgStack . pop ( ) ; count -= 2 ; frame = this . reverseDbgStack . last ( ) ; } } } else { while ( count < - 1 ) { frame . stack . push ( undefined ) ; count += 2 ; } } if ( interruptFound ) { const name = Remote . getInterruptName ( ) ; frame = new CallStackFrame ( 0 , nextSP , name ) ; this . reverseDbgStack . push ( frame ) ; } if ( ! frame ) { frame = new CallStackFrame ( 0 , sp , Remote . getMainName ( sp ) ) ; this . reverseDbgStack . push ( frame ) ; } frame . addr = nextPC ; } returns public async continue ( ) { this . running = true ; let nextLine ; let breakReasonString ; try { let currentLine = Z80Registers . getCache ( ) ; Utility . assert ( currentLine ) ; while ( this . running ) { nextLine = this . revDbgNext ( ) ; if ( ! nextLine ) { breakReasonString = 'Break: Reached start of instruction history.' ; break ; } this . handleReverseDebugStackForward ( currentLine , nextLine ) ; Z80Registers . setCache ( nextLine ) ; const condition = this . checkPcBreakpoints ( ) ; if ( condition != undefined ) { breakReasonString = condition ; break ; } currentLine = nextLine ; } } catch ( e ) { breakReasonString = 'Error occurred: ' + e . message ; } if ( ! nextLine ) { await Remote . getRegistersFromEmulator ( ) ; await Remote . getCallStackFromEmulator ( ) ; } return breakReasonString ; } returns public async reverseContinue ( ) { this . running = true ; await this . prepareReverseDbgStack ( ) ; let breakReason ; try { let prevLine = Z80Registers . getCache ( ) ; Utility . assert ( prevLine ) ; const pause = new TimeWait ( 100 , 1000 , 10 ) ; while ( this . running ) { await pause . waitAtInterval ( ) ; const currentLine = await this . revDbgPrev ( ) ; if ( ! currentLine ) { breakReason = 'Break: Reached end of instruction history.' ; break ; } await this . handleReverseDebugStackBack ( currentLine , prevLine ) ; Z80Registers . setCache ( currentLine ) ; const condition = this . checkPcBreakpoints ( ) ; if ( condition != undefined ) { breakReason = condition ; break ; } prevLine = currentLine ; } } catch ( e ) { breakReason = 'Break: Error occurred: ' + e . message ; } return breakReason ; } returns public getCurrentInstruction ( ) { let currentLine = Z80Registers . getCache ( ) ; Utility . assert ( currentLine ) ; const instruction = this . getInstruction ( currentLine ) ; return instruction ; } returns public async stepOver ( ) { this . running = true ; let currentLine = Z80Registers . getCache ( ) ; Utility . assert ( currentLine ) ; let nextLine ; const opcodes = this . decoder . getOpcodes ( currentLine ) ; const opcode0 = opcodes & 0xFF ; let pc = Z80Registers . decoder . parsePC ( currentLine ) ; let nextPC0 ; let nextPC1 ; if ( this . isCallOpcode ( opcode0 ) ) { nextPC0 = pc + 3 ; nextPC1 = nextPC0 ; } else if ( this . isRstOpcode ( opcode0 ) ) { nextPC0 = pc + 1 ; nextPC1 = nextPC0 + 1 ; } let breakReasonString ; try { while ( this . running ) { nextLine = this . revDbgNext ( ) ; if ( ! nextLine ) { breakReasonString = 'Break: Reached start of instruction history.' ; break ; } this . handleReverseDebugStackForward ( currentLine , nextLine ) ; if ( nextPC0 == undefined ) break ; pc = Z80Registers . decoder . parsePC ( nextLine ) ; if ( pc == nextPC0 || pc == nextPC1 ) break ; Z80Registers . setCache ( nextLine ) ; const condition = this . checkPcBreakpoints ( ) ; if ( condition != undefined ) { breakReasonString = condition ; break ; } currentLine = nextLine as string ; } } catch ( e ) { breakReasonString = e . message ; } if ( ! nextLine ) { await Remote . getRegistersFromEmulator ( ) ; await Remote . getCallStackFromEmulator ( ) ; } return breakReasonString ; } returns public async stepInto ( ) { let currentLine = Z80Registers . getCache ( ) ; Utility . assert ( currentLine ) ; let nextLine ; let breakReasonString ; try { nextLine = this . revDbgNext ( ) ; if ( nextLine ) { this . handleReverseDebugStackForward ( currentLine , nextLine ) ; } } catch ( e ) { breakReasonString = e . message ; } if ( ! nextLine ) { await Remote . getRegistersFromEmulator ( ) ; await Remote . getCallStackFromEmulator ( ) ; } return breakReasonString ; } returns public async stepOut ( ) { this . running = true ; let currentLine = Z80Registers . getCache ( ) ; Utility . assert ( currentLine ) ; let nextLine ; const startSP = Z80Registers . getSP ( ) ; let breakReason ; try { while ( this . running ) { nextLine = this . revDbgNext ( ) ; if ( ! nextLine ) { breakReason = 'Break: Reached start of instruction history.' ; break ; } this . handleReverseDebugStackForward ( currentLine , nextLine ) ; const flags = Z80Registers . decoder . parseAF ( currentLine ) ; const opcodes = CpuHistory . decoder . getOpcodes ( currentLine ) ; if ( CpuHistory . isRetAndExecuted ( opcodes , flags ) ) { const sp = Z80Registers . decoder . parseSP ( nextLine ) ; if ( sp > startSP ) { break ; } } Z80Registers . setCache ( nextLine ) ; const condition = this . checkPcBreakpoints ( ) ; if ( condition != undefined ) { breakReason = condition ; break ; } currentLine = nextLine as string ; } } catch ( e ) { breakReason = e . message ; } if ( ! nextLine ) { await Remote . getRegistersFromEmulator ( ) ; await Remote . getCallStackFromEmulator ( ) ; } return breakReason ; } } export let StepHistory ; export let CpuHistory ;