import { readFileSync } from 'fs' ; import { EventEmitter } from 'events' ; import { ChildProcess } from 'child_process' ; import * as child_process from 'child_process' ; import * as net from 'net' ; import * as path from 'path' ; import { StackFrame , Source } from 'vscode-debugadapter' ; export interface C64jasmBreakpoint { id : number ; line : number ; verified : boolean ; } export interface C64Regs { addr : number ; a : number ; x : number ; y : number ; sp : number ; v00 : number ; v01 : number ; flags : number ; line : number ; cycle : number ; stopwatch : number ; } type Cmd = 'next' | 'step' | 'pause' | undefined ; class MonitorConnection extends EventEmitter { private client : net . Socket ; private echo : ( str : string ) => void ; private prevCommand : Cmd ; private emitStepOrNext : number | undefined ; private emitBreak : number | undefined ; constructor ( echo ) { super ( ) ; this . echo = echo ; } write ( msg , resolve ) { this . echo ( 'VICE mon cmd: ' + msg ) ; this . client . write ( msg , resolve ) ; } connect ( ) { this . client = net . createConnection ( { port : 6510 , timeout : 5000 } , ( ) => { console . log ( 'Connected to VICE monitor' ) ; } ) ; this . client . on ( 'data' , data => { const lines = data . toString ( ) . split ( '\n' ) ; for ( let i = 0 ; i < lines . length ; i ++ ) { const line = lines [ i ] ; this . echo ( `'${ line }' len: ${ lines . length }` ) ; const breakRe = /^#([0-9]+) \(Stop on\s+exec ([0-9a-f]+)\).*/ ; let match = line . match ( breakRe ) ; if ( match ) { const addr = parseInt ( match [ 2 ] , 16 ) ; this . emitBreak = addr ; this . write ( 'r\n' , ( ) => { } ) ; continue ; } const regsRe = /^(\(C:\$([0-9a-f]+)\))?\s+ADDR A  X  Y  SP 00 01 NV-BDIZC LIN CYC  STOPWATCH/ ; const valsRe = /.;([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) ([01])+ ([0-9]+) ([0-9]+)\s+([0-9]+)/ ; if ( line . match ( regsRe ) ) { i ++ ; if ( i < lines . length ) { const line = lines [ i ] ; this . echo ( line ) ; const m = line . match ( valsRe ) ; if ( m ) { const vals = { addr : parseInt ( m [ 1 ] , 16 ) , a : parseInt ( m [ 2 ] , 16 ) , x : parseInt ( m [ 3 ] , 16 ) , y : parseInt ( m [ 4 ] , 16 ) , sp : parseInt ( m [ 5 ] , 16 ) , v00 : parseInt ( m [ 6 ] , 16 ) , v01 : parseInt ( m [ 7 ] , 16 ) , flags : parseInt ( m [ 8 ] , 2 ) , line : parseInt ( m [ 9 ] , 10 ) , cycle : parseInt ( m [ 10 ] , 10 ) , stopwatch : parseInt ( m [ 11 ] , 10 ) , } ; this . emit ( 'registers' , vals ) ; if ( this . emitStepOrNext ) { this . emit ( 'stopOnStep' , this . emitStepOrNext ) ; this . emitStepOrNext = undefined ; } if ( this . emitBreak ) { this . emit ( 'break' , this . emitBreak ) ; this . emitBreak = undefined ; } } } } if ( this . prevCommand == 'next' || this . prevCommand == 'step' ) { const stepRe = /^\.C:([0-9a-f]+)\s+.*/ ; match = line . match ( stepRe ) ; if ( match ) { const addr = parseInt ( match [ 1 ] , 16 ) ; this . emitStepOrNext = addr ; this . prevCommand = undefined ; continue ; } } if ( this . prevCommand == 'pause' ) { const curAddrRe = /^\(C:\$([0-9a-f]+)\)\s+.*/ ; match = line . match ( curAddrRe ) ; if ( match ) { const addr = parseInt ( match [ 1 ] , 16 ) ; this . emitStepOrNext = addr ; this . prevCommand = undefined ; continue ; } } } } ) ; } setBreakpoint ( pc ) { return new Promise ( resolve => { const cmd = `break ${ pc . toString ( 16 ) }\n` ; this . prevCommand = undefined ; this . write ( cmd , ( ) => resolve ( ) ) ; } ) ; } delBreakpoints ( ) { return new Promise ( resolve => { this . prevCommand = undefined ; this . write ( 'del\n' , ( ) => resolve ( ) ) ; } ) ; } go ( pc ? ) { return new Promise ( resolve => { const cmd = pc === undefined ? 'g' : `g ${ pc . toString ( 16 ) }` ; this . prevCommand = undefined ; this . write ( cmd + '\n' , ( ) => resolve ( ) ) ; } ) ; } next ( ) { return new Promise ( resolve => { this . prevCommand = 'next' ; this . write ( 'next\nr\n\n' , ( ) => resolve ( ) ) ; } ) ; } step ( ) { return new Promise ( resolve => { this . prevCommand = 'step' ; this . write ( 'step\nr\n\n' , ( ) => resolve ( ) ) ; } ) ; } pause ( ) { return new Promise ( resolve => { this . prevCommand = 'pause' ; this . write ( 'r\n\n' , ( ) => resolve ( ) ) ; } ) ; } quitEmulator ( ) { return new Promise ( resolve => { this . prevCommand = undefined ; this . write ( 'quit\n' , ( ) => resolve ( ) ) ; } ) ; } disass ( pc ? ) { return new Promise ( resolve => { const cmd = pc === undefined ? 'disass' : `disass ${ pc . toString ( 16 ) }` ; this . prevCommand = undefined ; this . write ( cmd + '\n' , ( ) => resolve ( ) ) ; } ) ; } rawCommand ( cmd ) { return new Promise ( resolve => { this . prevCommand = undefined ; this . write ( cmd + '\n' , ( ) => resolve ( ) ) ; } ) ; } loadProgram ( prgName , startAddress , stopOnEntry ) { return new Promise ( resolve => { this . prevCommand = 'step' ; const addrHex = startAddress . toString ( 16 ) ; if ( stopOnEntry ) { this . write ( `l "${ prgName }" 0 801\nbreak ${ addrHex }\ngoto ${ addrHex }\ndel\n` , ( ) => resolve ( ) ) ; } else { this . write ( `l "${ prgName }" 0 801\ngoto ${ addrHex }\n` , ( ) => resolve ( ) ) ; } } ) ; } } type C64jasmDebugInfo = { outputPrg : string ; debugInfo : { pcToLocs : { [ pc ] : { lineNo : number ; source : string ; } [ ] ; } ; } ; } ; function queryC64jasmDebugInfo ( ) { return new Promise ( ( resolve ) => { const port = 6502 ; const client = net . createConnection ( { port , timeout : 5000 } , ( ) => { client . write ( 'debug-info\r\n' ) ; } ) ; const chunks = [ ] ; client . on ( 'data' , data => { chunks . push ( data ) ; } ) . on ( 'end' , ( ) => { resolve ( JSON . parse ( Buffer . concat ( chunks ) . toString ( ) ) ) ; } ) ; } ) ; } function findSourceLoc ( c64jasm , path , line ) { if ( c64jasm ) { const pclocs = c64jasm . debugInfo . pcToLocs ; for ( const pc of Object . keys ( pclocs ) ) { const locList = pclocs [ pc ] ; for ( let i = 0 ; i < locList . length ; i ++ ) { const loc = locList [ i ] ; if ( loc . source == path && loc . lineNo == line ) { return parseInt ( pc , 10 ) ; } } } } return null ; } function sleep ( ms ) { return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } function parseBasicSysAddress ( progName ) { const buf = readFileSync ( progName ) ; if ( buf [ 0 ] == 0x01 && buf [ 1 ] == 0x08 && buf [ 6 ] == 0x9e ) { let offs = 7 ; let addr = 0 ; while ( buf [ offs ] != 0 ) { addr *= 10 ; addr += buf [ offs ] - 0x30 ; offs ++ ; } return addr ; } throw new Error ( 'couldn\'t parse entry point address' ) ; } /**
 * A C64jasm runtime with minimal debugger functionality.
 */ export class C64jasmRuntime extends EventEmitter { private _stoppedAddr = 0 ; private _breakPoints = new Map < string , C64jasmBreakpoint [ ] > ( ) ; private _breakpointId = 1 ; private _viceProcess : ChildProcess = null ; private _monitor : MonitorConnection ; private _debugInfo : C64jasmDebugInfo = null ; constructor ( ) { super ( ) ; } /**
     * Start executing the given program.
     */ public async start ( program , stopOnEntry , vicePath ? ) { this . _debugInfo = await queryC64jasmDebugInfo ( ) ; const startAddress = parseBasicSysAddress ( program ) ; this . _viceProcess = child_process . exec ( `${ vicePath || "x64" } -remotemonitor` ) ; await sleep ( 6000 ) ; const echoLog = ( logMsg ) => { this . sendEvent ( 'output' , logMsg ) ; } ; this . _monitor = new MonitorConnection ( echoLog ) ; this . _monitor . on ( 'break' , breakAddr => { this . _stoppedAddr = breakAddr ; this . sendEvent ( 'stopOnBreakpoint' ) ; } ) ; this . _monitor . on ( 'stopOnStep' , breakAddr => { this . _stoppedAddr = breakAddr ; this . sendEvent ( 'stopOnStep' ) ; } ) ; this . _monitor . on ( 'registers' , ( regs ) => { this . sendEvent ( 'registers' , regs ) ; } ) ; this . _monitor . connect ( ) ; this . _monitor . loadProgram ( program , startAddress , stopOnEntry ) ; this . _viceProcess . on ( 'close' , ( code , signal ) => { this . sendEvent ( 'end' ) ; } ) ; } public terminate ( ) { return this . _monitor . quitEmulator ( ) ; } /**
     * Continue execution.
     */ public continue ( ) { return this . _monitor . go ( ) ; } public step ( ) { return this . _monitor . step ( ) ; } public next ( ) { return this . _monitor . next ( ) ; } public pause ( ) { return this . _monitor . pause ( ) ; } private findSourceLineByAddr ( addr ) { const info = this . _debugInfo . debugInfo . pcToLocs [ addr ] [ 0 ] ; if ( info ) { return { src : new Source ( path . basename ( info . source ) , info . source ) , line : info . lineNo } ; } return undefined ; } /**
     * Returns a stack trace for the addres where we're currently stopped at.
     */ public stack ( ) { if ( this . _debugInfo && this . _debugInfo . debugInfo ) { const { src , line } = this . findSourceLineByAddr ( this . _stoppedAddr ) ; return new StackFrame ( 1 , src . name , src , line ) ; } return undefined ; } public async setBreakPoint ( path , line ) { const bp = < C64jasmBreakpoint > { verified : false , line , id : this . _breakpointId ++ } ; let bps = this . _breakPoints . get ( path ) ; if ( ! bps ) { bps = new Array < C64jasmBreakpoint > ( ) ; this . _breakPoints . set ( path , bps ) ; } bps . push ( bp ) ; await this . verifyBreakpoints ( path ) ; return bp ; } public async clearBreakpoints ( path ) { await this . _monitor . delBreakpoints ( ) ; this . _breakPoints . delete ( path ) ; } public disass ( pc ? ) { return this . _monitor . disass ( pc ) ; } public rawCommand ( c ) { return this . _monitor . rawCommand ( c ) ; } private async verifyBreakpoints ( path ) { await this . _monitor . delBreakpoints ( ) ; let bps = this . _breakPoints . get ( path ) ; if ( bps ) { for ( const bp of bps ) { if ( ! bp . verified ) { const addr = findSourceLoc ( this . _debugInfo , path , bp . line ) ; if ( addr ) { bp . verified = true ; await this . _monitor . setBreakpoint ( addr ) ; this . sendEvent ( 'breakpointValidated' , bp ) ; } else { console . log ( 'XXX unable find' , bp ) ; } } } } } private sendEvent ( event , ... args ) { setImmediate ( _ => { this . emit ( event , ... args ) ; } ) ; } }