import { Z80Registers } from '../remotes/z80registers' ; import { HistoryInstructionInfo } from './decodehistinfo' ; import { BaseMemory } from '../disassembler/basememory' ; import { Opcode } from '../disassembler/opcode' ; import { EventEmitter } from 'events' ; import { CallStackFrame } from '../callstackframe' ; import { RefList } from '../misc/reflist' ; import { Remote } from '../remotes/remotebase' ; import { Utility } from '../misc/utility' ; import { Settings } from '../settings' ; /**
 * This class takes care of the step history.
 * This is a lite version of the cpu history.
 *
 * The step history contains only the instructions that one has stepped through
 * during debugging.
 * E.g. if you step over a CALL the step history includes only the instruction CALL.
 * The true cpu history, in contrast, would include all instructions from the subroutine
 * that was called.
 *
 * The step history has the advantage that it is available for all Remotes.
 * Even if the remote itself does not support reverse debugging.
 * But, of course, it is less powerful.
 *
 * For the step history some stepping commands do not make sense or work
 * differently as in the true cpu history:
 * - Step over/step into: There is no differentiation. Both simply step to the next instruction.
 * - StepOut: Does not make sense. Does not exist.
 * - StepBack: Simply steps back to the next instruction.
 * - Reverse continue: Steps back until a breakpoint is hit (only breakpoint
 *   that happen to be at the steps) or until the start of the history.
 * - Continue: Steps forward until a breakpoint is hit (only breakpoint
 *   that happen to be at the steps) or until the end of the history.
 *
 * This class also holds the arrays for the 'revDbgHistory' decoration.
 * And it handles continue, stepOver, stepInto, stepOut, continueReverse
 * and stepBack while in step-back (reverse debugging mode) mode.
 */ export class StepHistoryClass extends EventEmitter { protected history : Array < HistoryInstructionInfo > ; protected historyIndex : number ; protected presentRegistersCache : any ; protected maxSize : number ; protected revDbgHistory : Array < number > ; protected liteCallStackHistory : Array < RefList < CallStackFrame > > ; protected running : boolean ; protected spotCount : number ; protected spotShowRegisters : boolean ; protected wantedChangedRegs = [ "A" , "F" , "BC" , "DE" , "HL" , "IX" , "IY" , "SP" ] ; constructor ( ) { super ( ) ; } /**
     * Init.
     */ public init ( ) { this . maxSize = Settings . launch . history . reverseDebugInstructionCount ; this . history = new Array < HistoryInstructionInfo > ( ) ; this . historyIndex = - 1 ; this . revDbgHistory = new Array < number > ( ) ; this . liteCallStackHistory = new Array < RefList < CallStackFrame > > ( ) ; this . spotCount = Settings . launch . history . spotCount ; this . spotShowRegisters = Settings . launch . history . spotShowRegisters ; } /**
     * Sets/gets the decoder to use.
     * The decoder of the instruction lines. Is the register encoder
     * for StepHistory and an enhanced decoder for CpuHistory.
     */ private _decoder : HistoryInstructionInfo ; public get decoder ( ) : HistoryInstructionInfo { return this . _decoder ; } ; public set decoder ( value ) { this . _decoder = value ; } ; /**
     * Clears the history cache. Is called on each "normal (forward)" step.
     */ public clear ( ) { } returns public async getPrevRegistersAsync ( ) { const index = this . historyIndex + 1 ; Utility . assert ( index >= 0 ) ; if ( index >= this . history . length ) return undefined ; this . historyIndex = index ; const regs = this . history [ index ] ; return regs ; } returns public getNextRegisters ( ) { let currentLine ; Utility . assert ( this . historyIndex >= 0 ) ; this . historyIndex -- ; if ( this . historyIndex >= 0 ) currentLine = this . history [ this . historyIndex ] ; return currentLine ; } /**
     * Returns the call stack at the historyIndex.
     */ public getCallStack ( ) { Utility . assert ( this . historyIndex >= 0 ) ; return this . liteCallStackHistory [ this . historyIndex ] ; } param line public pushHistoryInfo ( line ) { Utility . assert ( line ) ; this . history . unshift ( line ) ; if ( this . history . length > this . maxSize ) this . history . pop ( ) ; } /**
     * Pushes a callstack to the array.
     * If it is called it is called after 'pushHistoryInfo' to check the length correctly.
     */ public pushCallStack ( callstack ) { Utility . assert ( callstack ) ; this . liteCallStackHistory . unshift ( callstack ) ; if ( this . liteCallStackHistory . length > this . maxSize ) this . liteCallStackHistory . pop ( ) ; Utility . assert ( this . liteCallStackHistory . length == this . history . length ) ; } param line returns public getAddress ( line ) { const addr = Z80Registers . decoder . parsePC ( line ) ; return addr ; } returns public isInStepBackMode ( ) { return ( this . historyIndex >= 0 ) ; } param line returns public getInstruction ( line ) { let opcodes = this . decoder . getOpcodes ( line ) ; const pc = Z80Registers . decoder . parsePC ( line ) ; const buffer = new BaseMemory ( pc , 4 ) ; for ( let i = 0 ; i < 4 ; i ++ ) { const opc = opcodes & 0xFF ; buffer . setValueAtIndex ( i , opc ) ; opcodes >>>= 8 ; } const opcode = Opcode . getOpcodeAt ( buffer , pc ) ; const opCodeDescription = opcode . disassemble ( ) ; const instr = opCodeDescription . mnemonic ; return instr ; } /**
     * Emits 'revDbgHistory' to signal that the files should be decorated.
     */ protected emitRevDbgHistory ( ) { const addrSet = new Set ( this . revDbgHistory ) ; this . emit ( 'revDbgHistory' , addrSet ) ; } param i public getPreviousAddress ( i ) { let k = this . historyIndex + i ; if ( k >= this . history . length ) return undefined ; const line = this . history [ k ] ; const addr = this . getAddress ( line ) ; return addr ; } param line param regsMap protected getChangedRegistersString ( line , regsMap ) { let regText = '' ; for ( const [ regName , prevValue ] of regsMap ) { const regValue = Z80Registers . decoder . getRegValueByName ( regName , line ) ; if ( regValue != prevValue ) { let regName2 = '' ; let regValueString = '' ; const size = regName . length ; if ( size == 1 ) { regName2 = regName ; if ( regName == 'F' ) { regValueString = Utility . getFlagsString ( regValue ) ; } else { regValueString = Utility . getHexString ( regValue , 2 ) + 'h' ; } } else { if ( regName . startsWith ( 'I' ) || regName == 'SP' || regName == 'PC' ) { regName2 = regName ; regValueString = Utility . getHexString ( regValue , 4 ) ; } else { const valueXored = regValue ^ prevValue ; if ( valueXored & 0xFF00 ) { regName2 += regName [ 0 ] ; regValueString += Utility . getHexString ( regValue >>> 8 , 2 ) ; } if ( valueXored & 0xFF ) { regName2 += regName [ 1 ] ; regValueString += Utility . getHexString ( regValue & 0xFF , 2 ) ; } } regValueString += 'h' ; } if ( regText ) regText += ' ' ; regText += regName2 + '=' + regValueString ; regsMap . set ( regName , regValue ) ; } } return regText ; } param indices returns protected calcSpotHistoryAddressesAndRegisters ( indices ) { let registers ; let regsMap ; if ( this . spotShowRegisters ) { registers = new Array < string > ( ) ; regsMap = new Map < string , number > ( ) ; } const addresses = new Array < number > ( ) ; for ( let i = indices . length - 1 ; i >= 0 ; i -- ) { const index = indices [ i ] ; const line = this . history [ index ] ; const pc = Z80Registers . decoder . parsePCLong ( line ) ; addresses . unshift ( pc ) ; if ( registers ) { if ( i == indices . length - 1 ) { this . wantedChangedRegs . forEach ( regName => { const value = Z80Registers . decoder . getRegValueByName ( regName , line ) ; regsMap . set ( regName , value ) ; } ) ; } else { const regText = this . getChangedRegistersString ( line , regsMap ) ; registers . unshift ( regText ) ; } } } let currentRegs ; if ( this . isInStepBackMode ( ) ) currentRegs = this . presentRegistersCache ; else currentRegs = Z80Registers . getCache ( ) ; const regText = this . getChangedRegistersString ( currentRegs , regsMap ) ; registers . unshift ( regText ) ; return { addresses , registers } ; } /**
     * Emits 'historySpot' to signal that the files should be decorated.
     * It can happen that this method has to retrieve data from the
     * remote.
     */ protected emitHistorySpot ( ) { const count = this . spotCount ; if ( count <= 0 ) return ; let index = this . historyIndex + 1 ; let startIndex = index - count ; if ( startIndex < 0 ) startIndex = 0 ; let end = index + count ; if ( end > this . history . length ) end = this . history . length ; const indices = new Array < number > ( ) ; let i ; for ( i = startIndex ; i < end ; i ++ ) { indices . push ( i ) ; } const { addresses , registers } = this . calcSpotHistoryAddressesAndRegisters ( indices ) ; this . emit ( 'historySpot' , startIndex , addresses , registers ) ; } /**
     * Emits 'revDbgHistory' and 'historySpot' if configured.
     */ public emitHistory ( ) { this . emitRevDbgHistory ( ) ; this . emitHistorySpot ( ) ; } returns protected checkPcBreakpoints ( ) { Utility . assert ( Z80Registers . getCache ( ) ) ; let condition ; const callStack = this . getCallStack ( ) ; const len = callStack . length ; Utility . assert ( len > 0 ) ; const pc = callStack [ len - 1 ] . addr ; const breakpoints = Remote . getBreakpointsArray ( ) ; for ( const bp of breakpoints ) { if ( bp . address == pc ) { if ( ! bp . condition ) { condition = "" ; break ; } try { const result = Utility . evalExpression ( bp . condition , true ) ; if ( result != 0 ) { condition = bp . condition ; break ; } } catch ( e ) { condition = "Could not evaluate: " + bp . condition ; break ; } } } let reason ; if ( condition != undefined ) { const breakAddress = pc ; const addrString = Utility . getHexString ( breakAddress & 0xFFFF , 4 ) ; let bankString = "" ; const bank = breakAddress >>> 16 ; if ( bank != 0 ) bankString = " (bank=" + ( bank - 1 ) . toString ( ) + ")" ; reason = "Breakpoint hit @" + addrString + "h" + bankString ; if ( condition != "" ) reason += ', ' + condition ; } return reason ; } returns public async revDbgPrev ( ) { if ( ! this . isInStepBackMode ( ) ) { this . presentRegistersCache = Z80Registers . getCache ( ) ; } const line = await this . getPrevRegistersAsync ( ) ; if ( line ) { Z80Registers . setCache ( line ) ; const addr = Z80Registers . getPCLong ( ) ; this . revDbgHistory . push ( addr ) ; } return line ; } returns public revDbgNext ( ) { let line = this . getNextRegisters ( ) as string ; if ( line ) Z80Registers . setCache ( line ) ; else { Z80Registers . setCache ( this . presentRegistersCache ) ; } this . revDbgHistory . pop ( ) ; return line ; } 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 ) break ; 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 ( ) ; breakReasonString = 'Break: Reached start of instruction history.' ; } return breakReasonString ; } returns public async reverseContinue ( ) { this . running = true ; let currentLine ; let breakReasonString ; try { while ( this . running ) { currentLine = await this . revDbgPrev ( ) ; if ( ! currentLine ) { breakReasonString = 'Break: Reached end of instruction history.' ; break ; } Z80Registers . setCache ( currentLine ) ; const condition = this . checkPcBreakpoints ( ) ; if ( condition != undefined ) { breakReasonString = condition ; break ; } } } catch ( e ) { breakReasonString = 'Error occurred: ' + e . message ; } return breakReasonString ; } returns public getCurrentInstruction ( ) { return undefined ; } returns public async stepOver ( ) { let breakReasonString ; try { const currentLine = this . revDbgNext ( ) ; if ( ! currentLine ) throw Error ( 'Break: Reached start of instruction history.' ) ; } catch ( e ) { breakReasonString = e . message ; } return breakReasonString ; } returns public async stepInto ( ) { return 'Step-into not supported in lite reverse debugging.' ; } returns public async stepOut ( ) { return 'Step-out not supported in lite reverse debugging.' ; } returns public async stepBack ( ) { let breakReasonString ; try { const currentLine = await this . revDbgPrev ( ) ; if ( ! currentLine ) throw Error ( 'Break: Reached end of instruction history.' ) ; } catch ( e ) { breakReasonString = e . message ; } return breakReasonString ; } /**
     * User pressed break (pause).
     * Interrupts a running 'continue', 'continueReverse', 'stepOver' or 'stepOut'.
     */ public pause ( ) { this . running = false ; } }