import * as assert from 'assert' ; import { DelayedLog } from './delayedlog' ; import { MAX_MEM_SIZE } from './basememory' ; import { Memory , MemAttribute } from './memory' ; import { Opcode , OpcodeFlag } from './opcode' ; import { NumberType , getNumberTypeAsString } from './numbertype' ; import { DisLabel } from './dislabel' ; import { Comment } from './comment' ; import { SubroutineStatistics } from './statistics' ; import { EventEmitter } from 'events' ; import { Format } from './format' ; import { readFileSync } from 'fs' ; /**
 * The main Disassembler class.
 */ export class Disassembler extends EventEmitter { public funcAssignLabels : ( address : number ) => string ; public memory = new Memory ( ) ; public labels : Map < number , DisLabel > ; protected revertedLabelMap : Map < string , number > ; protected offsetLabels : Map < number , number > ; protected addressParents : Array < DisLabel > ; protected addressQueue = new Array < number > ( ) ; protected addressComments = new Map < number , Comment > ( ) ; protected subroutineStatistics = new Map < DisLabel , SubroutineStatistics > ( ) ; protected statisticsMax : SubroutineStatistics = { sizeInBytes : 0 , countOfInstructions : 0 , CyclomaticComplexity : 0 } ; protected statisticsMin : SubroutineStatistics = { sizeInBytes : Number . MAX_SAFE_INTEGER , countOfInstructions : Number . MAX_SAFE_INTEGER , CyclomaticComplexity : Number . MAX_SAFE_INTEGER } ; protected dotMarkedLabels = new Map < number | string , string > ( ) ; protected dotWarningMark = 'lightblue' ; public opcodesLowerCase = true ; public numberOfLinesBetweenBlocks = 2 ; public addReferencesToSubroutines = true ; public addReferencesToAbsoluteLabels = true ; public addReferencesToRstLabels = true ; public addReferencesToDataLabels = true ; public addOpcodeBytes = true ; public labelSubPrefix = "SUB" ; public labelLblPrefix = "LBL" ; public labelRstPrefix = "RST" ; public labelDataLblPrefix = "DATA" ; public labelSelfModifyingPrefix = "SELF_MOD" ; public labelLocalLablePrefix = "_l" ; public labelLoopPrefix = "_loop" ; public labelIntrptPrefix = "INTRPT" ; public automaticAddresses = true ; public clmnsAddress = 5 ; public clmnsBytes = 4 * 3 + 1 ; public clmnsOpcodeFirstPart = 4 + 1 ; public clmsnOpcodeTotal = 5 + 6 + 1 ; public dotFormatString = "rankdir=TB;" ; public nodeFormatString = "${label}\\n0x${address}\\nSize=${size}\\n" ; public rstDontFollowAddresses = new Array < number > ( ) ; public graphLabels = new Array < string | number > ( ) ; protected disassembledLines : Array < string > ; protected snaStartAddress = - 1 ; protected emptyLinesWritten = false ; protected DBG_COLLECT_LABELS = false ; protected DBG_ADD_DEC_ADDRESS = false ; protected no_warning_disassemble_unassigned_memory = false ; public disableCommentsInDisassembly = false ; /**
     * Initializes the Opcode formatting.
     */ constructor ( ) { super ( ) ; this . initLabels ( ) ; Opcode . setConvertToLabelHandler ( value => { let valueName ; let label ; let offsString = '' ; if ( this . labels ) label = this . labels . get ( value ) ; if ( ! label ) { const offs = this . offsetLabels . get ( value ) ; if ( offs ) { label = this . labels . get ( value + offs ) ; if ( label ) offsString = ( offs > 0 ) ? '' + ( - offs ) : '+' + ( - offs ) ; } } if ( label ) valueName = label . name + offsString ; return valueName ; } ) ; } /**
     * Adds address 0 to the labels if it has not been added already
     * or the SNA address.
     */ public addAutomaticAddresses ( ) { if ( ! this . automaticAddresses ) return ; if ( this . snaStartAddress >= 0 ) { this . addressQueue . push ( this . snaStartAddress ) ; } else { if ( this . memory . getAttributeAt ( 0 ) & MemAttribute . ASSIGNED ) { let label0 = this . labels . get ( 0 ) ; if ( ! label0 ) { this . setFixedCodeLabel ( 0 , 'ORG_0000' ) ; } else { label0 . type = NumberType . CODE_LBL ; } this . addressQueue . push ( 0 ) ; } } } /**
     * Returns the disassembled lines as an array of strings.
     * Make sure to run 'disassemble' beforehand.
     */ public getDisassemblyLines ( ) { if ( ! this . disassembledLines ) { this . emit ( 'warning' , 'No disassembly was done.' ) ; return [ '' ] ; } return this . disassembledLines ; } /**
     * Returns the disassembled lines as a string.
     * Make sure to run 'disassemble' beforehand.
     */ public getDisassemblyText ( ) { return this . getDisassemblyLines ( ) . join ( '\n' ) ; } /**
     * Disassembles the  memory area.
     * Disassembly is done in a few passes.
     * Afterwards the disassembledLines are set:
     * An array of strings with the disassembly.
     */ public disassemble ( ) { this . addAutomaticAddresses ( ) ; this . collectLabels ( ) ; this . findInterruptLabels ( ) ; this . setSpecialLabels ( ) ; this . sortLabels ( ) ; this . adjustCodePointingLabels ( ) ; this . addFlowThroughReferences ( ) ; this . turnLBLintoSUB ( ) ; this . findLocalLabelsInSubroutines ( ) ; this . addParentReferences ( ) ; this . addCallsListToLabels ( ) ; this . countStatistics ( ) ; this . assignLabelNames ( ) ; if ( ! this . disableCommentsInDisassembly ) this . addLabelComments ( ) ; const disLines = this . disassembleMemory ( ) ; this . disassembledLines = this . getEquLabelsDisassembly ( ) ; this . disassembledLines . push ( ... disLines ) ; while ( this . disassembledLines . length ) { if ( this . disassembledLines [ 0 ] . length > 0 ) break ; this . disassembledLines . splice ( 0 , 1 ) ; } } param origin param memory public setMemory ( origin , memory ) { this . memory . setMemory ( origin , memory ) ; } param origin param path public readBinFile ( origin , path ) { const bin = readFileSync ( path ) ; this . setMemory ( origin , bin ) ; } param path public readSnaFile ( path ) { let sna = readFileSync ( path ) ; const header = sna . slice ( 0 , 27 ) ; const bin = sna . slice ( 27 ) ; const sp = header [ 23 ] + 256 * header [ 24 ] ; const spIndex = sp - 0x4000 ; const start = bin [ spIndex ] + 256 * bin [ spIndex + 1 ] ; this . setMemory ( 0x4000 , bin ) ; this . snaStartAddress = start ; this . no_warning_disassemble_unassigned_memory = true ; } /**
     * Clears all labels collected so far.
     * Useful for dot generation of a particular subroutine.
     */ public clearLabels ( ) { this . labels = new Map < number , DisLabel > ( ) ; this . offsetLabels = new Map < number , number > ( ) ; this . addressQueue = new Array < number > ( ) ; } /**
     * Clears all labels.
     * Is done at start of disassembly.
     */ public initLabels ( ) { this . labels = new Map < number , DisLabel > ( ) ; this . offsetLabels = new Map < number , number > ( ) ; this . addressParents = new Array < DisLabel > ( ) ; this . addressComments = new Map < number , Comment > ( ) ; } param address param name param type public setLabel ( address , name ? , type = NumberType . CODE_LBL ) { let label = this . labels . get ( address ) ; if ( label ) { if ( ! label . name || ! label . isFixed ) ( label . name as any ) = name ; return ; } label = new DisLabel ( type ) ; this . labels . set ( address , label ) ; ( label . name as any ) = name ; const attr = this . memory . getAttributeAt ( address ) ; if ( attr & MemAttribute . ASSIGNED ) { if ( type == NumberType . CODE_LBL || type == NumberType . CODE_LOCAL_LBL || type == NumberType . CODE_LOCAL_LOOP || type == NumberType . CODE_RST || type == NumberType . CODE_SUB ) this . addressQueue . push ( address ) ; } else { label . isEqu = true ; } } param address param name public setFixedCodeLabel ( address , name ? ) { let label = this . labels . get ( address ) ; if ( ! label ) { label = new DisLabel ( NumberType . CODE_LBL ) ; this . labels . set ( address , label ) ; } if ( name ) ( label . name as any ) = name ; const attr = this . memory . getAttributeAt ( address ) ; if ( attr & MemAttribute . ASSIGNED ) this . addressQueue . push ( address ) ; else label . isEqu = true ; label . isFixed = true ; } param queue public setAddressQueue ( queue ) { this . addressQueue = queue ; } param address param count public setJmpTable ( address , count ) { for ( let i = 0 ; i < count ; i ++ ) { let jmpAddress = this . memory . getWordValueAt ( address ) ; this . setFixedCodeLabel ( jmpAddress ) ; address += 2 ; } } param path public useMameTraceFile ( path ) { const trace = readFileSync ( path ) . toString ( ) ; if ( trace . length < 5 ) return ; const buffer = new Array < boolean > ( MAX_MEM_SIZE ) ; let k = 0 ; let jpHlRef ; do { const addressString = trace . substr ( k , 5 ) ; if ( addressString . length == 5 && addressString [ 4 ] == ':' ) { const addr = parseInt ( addressString , 16 ) ; buffer [ addr ] = true ; k += 5 ; const memAttr = this . memory . getAttributeAt ( addr ) ; if ( jpHlRef ) { this . setFoundLabel ( addr , new Set ( [ jpHlRef ] ) , NumberType . CODE_LBL , memAttr ) ; jpHlRef = undefined ; } if ( memAttr & MemAttribute . ASSIGNED ) { const opcodeString = trace . substr ( k , 10 ) ; if ( opcodeString == ' jp   (hl)' ) { jpHlRef = addr ; k += 10 ; } } } k = trace . indexOf ( '\n' , k ) + 1 ; } while ( k != 0 ) ; for ( let addr = 0 ; addr < MAX_MEM_SIZE ; addr ++ ) { if ( buffer [ addr ] ) this . addressQueue . push ( addr ) ; } } /**
     * Prints all labels to the console.
     */ returns public getEquLabelsDisassembly ( ) { let firstLabel = true ; const lines = new Array < string > ( ) ; for ( let [ address , label ] of this . labels ) { if ( label . isEqu ) { if ( firstLabel ) { if ( ! this . disableCommentsInDisassembly ) { lines . push ( '; EQU:' ) ; lines . push ( '; Data addresses used by the opcodes that point to uninitialized memory areas.' ) ; } firstLabel = false ; } const statement = Format . addSpaces ( label . name + ':' , this . clmnsBytes - 1 ) + ' ' + this . rightCase ( 'EQU ' ) + Format . fillDigits ( Format . getHexString ( address , 4 ) , ' ' , 5 ) + 'h' ; const comment = this . addressComments . get ( address ) ; const commentLines = Comment . getLines ( comment , statement , this . disableCommentsInDisassembly ) ; lines . push ( ... commentLines ) ; } } return lines ; } /**
     * Parses the memory area for opcodes with labels.
     * Stores all labels with a categorization in an array.
     * Priorization of labels:
     * 1. "SUB"-labels: Everything called by "CALL nn" or "CALL cc,nn"
     * 2. "LBL"-labels: Everything jumped to "JP nn" or "JP cc,nn"
     * 3. "loop"-labels: Everything jumped to by "DJNZ"
     * 4. "l"-labels: Everything jumped to by "JR n" or "JR cc,n"
     * "loop" and "lbl" labels are prefixed by a previous "SUB"- or "LBL"-label with a prefix
     * ".subNNN_" or ".lblNNN_". E.g. ".sub001_l5", ".sub001_loop1", ".lbl788_l89", ".lbl788_loop23".
     * All labels are stored into this.labels. At the end the list is sorted by the address.
     */ protected collectLabels ( ) { let address ; let opcode ; while ( ( address = this . addressQueue . shift ( ) ) != undefined ) { do { let attr = this . memory . getAttributeAt ( address ) ; if ( attr & MemAttribute . CODE ) break ; if ( ! ( attr & MemAttribute . ASSIGNED ) ) { if ( ! this . no_warning_disassemble_unassigned_memory ) this . emit ( 'warning' , 'Trying to disassemble unassigned memory area at 0x' + address . toString ( 16 ) + '.' ) ; break ; } opcode = Opcode . getOpcodeAt ( this . memory , address ) ; if ( this . DBG_COLLECT_LABELS ) console . log ( Format . getHexString ( address ) + '\t' + opcode . disassemble ( ) . mnemonic ) ; const len = opcode . length ; let memAddress = address ; let quitDoWhile = false ; for ( let i = 1 ; i < len ; i ++ ) { memAddress ++ ; attr = this . memory . getAttributeAt ( memAddress ) ; if ( attr & MemAttribute . CODE ) { const otherOpcode = Opcode . getOpcodeAt ( this . memory , memAddress ) ; this . emit ( 'warning' , 'Aborting disassembly: Ambiguous disassembly: Trying to disassemble opcode "' + opcode . name + '" at address 0x' + address . toString ( 16 ) + ' but address 0x' + memAddress . toString ( 16 ) + ' already contains opcode "' + otherOpcode . name + '".' ) ; quitDoWhile = true ; break ; } } if ( quitDoWhile ) break ; this . memory . addAttributeAt ( address , 1 , MemAttribute . CODE_FIRST ) ; this . memory . addAttributeAt ( address , opcode . length , MemAttribute . CODE ) ; if ( ! this . disassembleForLabel ( address , opcode ) ) { return ; } if ( opcode . flags & OpcodeFlag . STOP ) break ; address += opcode . length ; } while ( ! ( opcode . flags & OpcodeFlag . STOP ) ) ; if ( this . DBG_COLLECT_LABELS ) console . log ( '\n' ) ; } } param address param referenceAddresses param type param attr protected setFoundLabel ( address , referenceAddresses , type , attr ) { let label = this . labels . get ( address ) ; if ( label ) { if ( label . type < type ) label . type = type ; } else { label = new DisLabel ( type ) ; this . labels . set ( address , label ) ; if ( ! ( attr & MemAttribute . ASSIGNED ) ) label . isEqu = true ; } for ( let ref of referenceAddresses ) { if ( ref != address ) { label . references . add ( ref ) ; } } } /**
     * Sets the label for a possibly SNA start address and
     * labels that show where memory areas start if the memory area
     * is not continuous.
     */ protected setSpecialLabels ( ) { if ( this . automaticAddresses ) { if ( this . snaStartAddress >= 0 ) { const label = this . labels . get ( this . snaStartAddress ) ; if ( ! label ) this . setLabel ( this . snaStartAddress , 'SNA_LBL_MAIN_START_' + this . snaStartAddress . toString ( 16 ) . toUpperCase ( ) , NumberType . CODE_LBL ) ; } } let prevAttr = 0 ; for ( let addr = 0 ; addr < MAX_MEM_SIZE ; addr ++ ) { const memAttr = this . memory . getAttributeAt ( addr ) ; if ( ( prevAttr ^ memAttr ) & MemAttribute . ASSIGNED ) { if ( memAttr & MemAttribute . ASSIGNED ) { const label = this . labels . get ( addr ) ; if ( ! label ) { this . setLabel ( addr , 'BIN_START_' + Format . getHexString ( addr ) . toUpperCase ( ) , NumberType . DATA_LBL ) ; } } } prevAttr = memAttr ; } } /**
     * Finds interrupt labels. I.e. start of program code
     * that doesn't have any lable yet.
     * As z80dismblr uses CFG analysis this can normally not happen.
     * But if you e.g. provide a trace (tr) file this also includes interrupt traces.
     * So z80dismblr will also follow these paths, but since there
     * is no label associated this code would be presented without 'Start' label.
     * 'findInterruptLabels' finds these code parts and assigns a label.
     * Several rules are used:
     * - It is checked if a label exists at a change from data or unassigned to opcode area
     * - For a transition from stop code to opcode and there is no associated label
     *
     * Note: Another strategy to find interrupts would be to examine the trace from the
     * tr file. If an address change happens that is bigger than +/-128 it must be
     * at least a JP/CALL label or an interrupt call. The label can be added here.
     * Later the JP/CALL labels would be found anyway (most probably).
     */ protected findInterruptLabels ( ) { const foundInterrupts = new Array < number > ( ) ; let prevAttr = 0 ; let prevCodeAddr = - 1 ; for ( let address = 0x0000 ; address < MAX_MEM_SIZE ; address ++ ) { const memAttr = this . memory . getAttributeAt ( address ) ; if ( address != this . snaStartAddress ) { if ( memAttr & MemAttribute . CODE_FIRST && memAttr & MemAttribute . ASSIGNED ) { const label = this . labels . get ( address ) ; if ( ! label ) { if ( ! ( prevAttr & MemAttribute . ASSIGNED ) || ! ( prevAttr & MemAttribute . CODE ) ) { if ( address != this . snaStartAddress ) { this . setFixedCodeLabel ( address , this . labelIntrptPrefix ) ; foundInterrupts . push ( address ) ; } } else if ( prevCodeAddr >= 0 ) { const opcode = Opcode . getOpcodeAt ( this . memory , prevCodeAddr ) ; if ( opcode . flags & OpcodeFlag . STOP ) { this . setFixedCodeLabel ( address , this . labelIntrptPrefix ) ; foundInterrupts . push ( address ) ; } } } } } prevAttr = memAttr ; if ( ! ( memAttr & MemAttribute . CODE ) ) prevCodeAddr = - 1 ; if ( memAttr & MemAttribute . CODE_FIRST ) prevCodeAddr = address ; } const count = foundInterrupts . length ; if ( count > 1 ) { for ( let index = 0 ; index < count ; index ++ ) { const addr = foundInterrupts [ index ] ; const label = this . labels . get ( addr ) ; assert ( label , 'findInterruptLabels' ) ; if ( label ) label . name += index + 1 ; } } } /**
     * Sorts all labels by address.
     */ protected sortLabels ( ) { this . labels = new Map ( [ ... this . labels . entries ( ) ] . sort ( ( [ a ] , [ b ] ) => a - b ) ) ; } param opcode param opcodeAddress returns protected disassembleForLabel ( opcodeAddress , opcode ) { if ( opcode . flags & OpcodeFlag . BRANCH_ADDRESS ) { const branchAddress = opcode . value ; const attr = this . memory . getAttributeAt ( branchAddress ) ; let vType = opcode . valueType ; if ( vType == NumberType . CODE_LOCAL_LBL ) { if ( branchAddress <= opcodeAddress ) vType = NumberType . CODE_LOCAL_LOOP ; } else if ( vType == NumberType . CODE_LBL ) { if ( ! ( attr & MemAttribute . ASSIGNED ) ) vType = NumberType . CODE_SUB ; } this . setFoundLabel ( branchAddress , new Set ( [ opcodeAddress ] ) , vType , attr ) ; if ( ! ( attr & MemAttribute . CODE ) ) { if ( attr & MemAttribute . ASSIGNED ) { if ( vType != NumberType . CODE_RST || this . rstDontFollowAddresses . indexOf ( branchAddress ) < 0 ) { this . addressQueue . push ( branchAddress ) ; } } } } else if ( opcode . valueType == NumberType . DATA_LBL ) { let address = opcode . value ; if ( opcode . flags & OpcodeFlag . LOAD_STACK_TOP ) { const offs = - 2 ; this . offsetLabels . set ( address , offs ) ; address += offs ; if ( ! this . addressComments . get ( address ) ) { const comment = new Comment ( ) ; comment . addBefore ( '; Last element of stack:' ) ; this . addressComments . set ( address , comment ) ; } } const attr = this . memory . getAttributeAt ( address ) ; this . setFoundLabel ( address , new Set ( [ opcodeAddress ] ) , opcode . valueType , attr ) ; } return true ; } /**
     * Finds data labels that point into code areas.
     * If the pointer points to the start of the opcode nothing needs to be done here.
     * Everything is handled by the assignLabelNames method.
     * But if the pointer points into the middle of an instruction the label
     * need to be adjusted:
     * 1. The current label is exchanged with an offset label
     * 2. Another label is created at the start of the opcode.
     */ protected adjustCodePointingLabels ( ) { const changeMap = new Map < number , DisLabel > ( ) ; for ( let [ address , label ] of this . labels ) { switch ( label . type ) { case NumberType . CODE_LBL : case NumberType . CODE_LOCAL_LBL : case NumberType . CODE_LOCAL_LOOP : case NumberType . CODE_RST : case NumberType . CODE_SUB : case NumberType . DATA_LBL : const memAttr = this . memory . getAttributeAt ( address ) ; if ( memAttr & MemAttribute . CODE ) { if ( ! ( memAttr & MemAttribute . CODE_FIRST ) ) { changeMap . set ( address , label ) ; } } break ; } } for ( let [ address , label ] of changeMap ) { let addrStart = address ; let attr ; do { addrStart -- ; assert ( address - addrStart <= 4 , 'adjustSelfModifyingLabels' ) ; attr = this . memory . getAttributeAt ( addrStart ) ; } while ( ! ( attr & MemAttribute . CODE_FIRST ) ) ; this . setFoundLabel ( addrStart , label . references , label . type , attr ) ; this . labels . delete ( address ) ; const offs = addrStart - address ; this . offsetLabels . set ( address , offs ) ; } } /**
     * Check if a LBL/SUB references another LBL/SUB just by flow-through.
     * I.e in.
     * 	SUB01:
     * 		LD A,1
     * 	SUB02:
     * 		LD B,3
     * SUB01 would otherwise not reference SUB02 although it flows through which
     * is equivalent to a JP or CALL;RET.
     * This "references" are added here.
     * Note: This could add an address a reference to 2 different labels.
     * Note: Also works in labels.
     */ protected addFlowThroughReferences ( ) { for ( let [ address , label ] of this . labels ) { switch ( label . type ) { case NumberType . CODE_LBL : case NumberType . CODE_SUB : case NumberType . CODE_RST : const found = this . findNextFlowThroughLabel ( address ) ; if ( found ) { const foundLabel = found . label ; if ( label != foundLabel ) { foundLabel . references . add ( found . address ) ; } } } } } param address returns protected findNextFlowThroughLabel ( address ) { let memAttr = this . memory . getAttributeAt ( address ) ; if ( ! ( memAttr & MemAttribute . ASSIGNED ) ) { return undefined ; } let opcode = Opcode . getOpcodeAt ( this . memory , address ) ; while ( ! ( opcode . flags & OpcodeFlag . STOP ) ) { const prevAddress = address ; address += opcode . length ; const foundLabel = this . labels . get ( address ) ; if ( foundLabel ) { const type = foundLabel . type ; switch ( type ) { case NumberType . CODE_LBL : case NumberType . CODE_SUB : return { label : foundLabel , address : prevAddress } ; } } const memAttr = this . memory . getAttributeAt ( address ) ; if ( ! ( memAttr & MemAttribute . ASSIGNED ) ) { return undefined ; } opcode = Opcode . getOpcodeAt ( this . memory , address ) ; } return undefined ; } /**
     * Checks if LBLs are SUBs and if so turns them into SUBs.
     * Therefore it iterates through all LBL labels.
     * Then it walks through the LBL and stops if it finds a RET, RET cc or RETI.
     * Note 1: It does not check necessarily all branches. Once it finds a
     * RET it assumes that also the other branches will end with a RET.
     * Note 2: When this function is done there should be only 2 LBL left:
     * - the main program and
     * - the interrupt.
     */ protected turnLBLintoSUB ( ) { for ( let [ address , label ] of this . labels ) { if ( label . type == NumberType . CODE_LBL ) { DelayedLog . startLog ( ) ; const addrsArray = new Array < number > ( ) ; const retFound = this . findRET ( address , addrsArray ) ; if ( retFound ) { DelayedLog . logIf ( address , ( ) => 'Addresses: ' + addrsArray . map ( addr => addr . toString ( 16 ) ) . join ( ' ' ) ) ; DelayedLog . logIf ( address , ( ) => 'turnLBLintoSUB: Turned Label ' + getNumberTypeAsString ( label . type ) + ' into CODE_SUB.' ) ; label . type = NumberType . CODE_SUB ; } DelayedLog . stopLog ( ) ; } } } param address param addrsArray returns protected findRET ( address , addrsArray ) { let opcodeClone ; do { const memAttr = this . memory . getAttributeAt ( address ) ; if ( ! ( memAttr & MemAttribute . ASSIGNED ) ) { DelayedLog . log ( ( ) => 'findRET: address=' + DelayedLog . getNumber ( address ) + ': returns. memory not assigned.' ) ; return false ; } if ( addrsArray . indexOf ( address ) >= 0 ) { DelayedLog . log ( ( ) => 'findRET: address=' + DelayedLog . getNumber ( address ) + ': returns. memory already checked.' ) ; return false ; } const addrLabel = this . labels . get ( address ) ; if ( addrLabel ) { const type = addrLabel . type ; if ( type == NumberType . CODE_SUB || type == NumberType . CODE_RST ) { DelayedLog . log ( ( ) => 'findRET: address=' + DelayedLog . getNumber ( address ) + ': SUB FOUND. address belongs already to a SUB.' ) ; return true ; } } const opcode = Opcode . getOpcodeAt ( this . memory , address ) ; opcodeClone = { ... opcode } ; if ( opcodeClone . flags & OpcodeFlag . RET ) { DelayedLog . log ( ( ) => 'findRET: address=' + DelayedLog . getNumber ( address ) + ': SUB FOUND. RET code = ' + opcodeClone . name + '.' ) ; return true ; } addrsArray . push ( address ) ; if ( opcodeClone . flags & OpcodeFlag . BRANCH_ADDRESS ) { if ( ! ( opcodeClone . flags & OpcodeFlag . CALL ) ) { const branchAddress = opcodeClone . value ; DelayedLog . log ( ( ) => 'findRET: address=' + DelayedLog . getNumber ( address ) + ': branching to ' + DelayedLog . getNumber ( branchAddress ) + '.' ) ; DelayedLog . pushTab ( ) ; const res = this . findRET ( branchAddress , addrsArray ) ; DelayedLog . popTab ( ) ; if ( res ) return true ; } } address += opcodeClone . length ; } while ( ! ( opcodeClone . flags & OpcodeFlag . STOP ) ) ; return false ; } /**
     * After Labels have been assigned:
     * Iterate through all subroutine labels.
     * Walkthrough each subroutine and store the address belonging to the
     * subroutine in an (temporary) array. The subroutine ends if each branch
     * ends with a stop code (RET or JP).
     * Then iterate the array.
     * Each address with a Label of type CODE_LBL/SUB is checked. If it contains
     * reference addresses outside the range of the array then it stays a CODE_LBL/SUB
     * otherwise it is turned into a local label CODE_LOCAL_LBL or CODE_LOCAL_LOOP.
     */ protected findLocalLabelsInSubroutines ( ) { for ( let [ address , label ] of this . labels ) { switch ( label . type ) { case NumberType . CODE_LBL : case NumberType . CODE_SUB : case NumberType . CODE_RST : DelayedLog . startLog ( ) ; const addrsArray = new Array < number > ( ) ; this . getSubroutineAddresses ( address , addrsArray ) ; this . reduceSubroutineAddresses ( address , addrsArray ) ; for ( let addr of addrsArray ) { if ( addr == address ) continue ; const addrLabel = this . labels . get ( addr ) ; if ( ! addrLabel ) continue ; if ( addrLabel . type != NumberType . CODE_LBL && addrLabel . type != NumberType . CODE_SUB ) continue ; if ( addrLabel . isFixed ) continue ; const refs = addrLabel . references ; let outsideFound = false ; for ( const ref of refs ) { if ( addrsArray . indexOf ( ref ) < 0 ) { outsideFound = true ; break ; } } if ( ! outsideFound ) { DelayedLog . logIf ( addr , ( ) => 'Addresses: ' + addrsArray . map ( addr => addr . toString ( 16 ) ) . join ( ' ' ) ) ; DelayedLog . logIf ( addr , ( ) => 'findLocalLabelsInSubroutines: Turned Label' + getNumberTypeAsString ( addrLabel . type ) + ' into CODE_LOCAL_LBL.' ) ; addrLabel . type = NumberType . CODE_LOCAL_LBL ; for ( const ref of refs ) { const diff = ref - addr ; if ( diff >= 0 && diff <= 128 ) { addrLabel . type = NumberType . CODE_LOCAL_LOOP ; break ; } } } } DelayedLog . stopLog ( ) ; } } } param address param addrsArray protected getSubroutineAddresses ( address , addrsArray ) { let opcodeClone ; do { const memAttr = this . memory . getAttributeAt ( address ) ; if ( ! ( memAttr & MemAttribute . ASSIGNED ) ) { DelayedLog . log ( ( ) => 'getSubroutineAddresses: address=' + DelayedLog . getNumber ( address ) + ': returns. memory not assigned.' ) ; break ; } if ( addrsArray . indexOf ( address ) >= 0 ) { DelayedLog . log ( ( ) => 'getSubroutineAddresses: address=' + DelayedLog . getNumber ( address ) + ': returns. memory already checked.' ) ; break ; } const opcode = Opcode . getOpcodeAt ( this . memory , address ) ; opcodeClone = { ... opcode } ; addrsArray . push ( address ) ; if ( opcodeClone . flags & OpcodeFlag . BRANCH_ADDRESS ) { if ( ! ( opcodeClone . flags & OpcodeFlag . CALL ) ) { const branchAddress = opcodeClone . value ; DelayedLog . log ( ( ) => 'getSubroutineAddresses: address=' + DelayedLog . getNumber ( address ) + ': branching to ' + DelayedLog . getNumber ( branchAddress ) + '.' ) ; DelayedLog . pushTab ( ) ; this . getSubroutineAddresses ( branchAddress , addrsArray ) ; DelayedLog . popTab ( ) ; } } address += opcodeClone . length ; } while ( ! ( opcodeClone . flags & OpcodeFlag . STOP ) ) ; } param address param addrsArray protected reduceSubroutineAddresses ( address , addrsArray ) { addrsArray . sort ( ( a , b ) => a - b ) ; const array = addrsArray . filter ( value => value >= address ) ; addrsArray . length = 0 ; let nextAddress = address ; for ( const addr of array ) { if ( addr != nextAddress ) break ; const opcode = Opcode . getOpcodeAt ( this . memory , addr ) ; addrsArray . push ( nextAddress ) ; nextAddress = addr + opcode . length ; } } /**
     * Iterates through all Labels.
     * Fills the 'this.addressParents' array with the parent references for each address.
     * Afterwards it removes all self-references in Labels.references.
     */ protected addParentReferences ( ) { for ( let [ address , label ] of this . labels ) { const type = label . type ; if ( type == NumberType . CODE_SUB || type == NumberType . CODE_RST || type == NumberType . CODE_LBL ) { DelayedLog . startLog ( ) ; this . setSubroutineParent ( address , label ) ; DelayedLog . logIf ( address , ( ) => '' + Format . getHexString ( address , 4 ) + ' processed.' ) ; DelayedLog . stopLog ( ) ; } } for ( let [ address , label ] of this . labels ) { const refs = label . references ; let anyRefOutside = false ; for ( let ref of refs ) { const addr = ref ; const parentLabel = this . addressParents [ addr ] ; if ( parentLabel == label ) { const memAttr = this . memory . getAttributeAt ( ref ) ; assert ( memAttr & MemAttribute . ASSIGNED ) ; assert ( memAttr & MemAttribute . CODE ) ; assert ( memAttr & MemAttribute . CODE_FIRST ) ; const opcode = Opcode . getOpcodeAt ( this . memory , ref ) ; if ( ! ( opcode . flags & OpcodeFlag . CALL ) ) { refs . delete ( ref ) ; } } else { anyRefOutside = true ; } } if ( ! anyRefOutside && ( label . type == NumberType . CODE_SUB || label . type == NumberType . CODE_RST ) && refs . size > 0 ) { this . emit ( 'warning' , 'Address: ' + Format . getHexString ( address , 4 ) + 'h. A subroutine was found that calls itself recursively but is not called from any other location.' ) ; this . dotMarkedLabels . set ( address , this . dotWarningMark ) ; } } } param addr param parentLabel protected setSubroutineParent ( addr , parentLabel ) { let opcodeClone ; let address = addr ; do { const memAttr = this . memory . getAttributeAt ( address ) ; if ( ! ( memAttr & MemAttribute . ASSIGNED ) ) { DelayedLog . log ( ( ) => 'setSubroutineParent: address=' + DelayedLog . getNumber ( address ) + ': returns. memory not assigned.' ) ; break ; } const memLabel = this . addressParents [ address ] ; if ( memLabel ) { DelayedLog . log ( ( ) => 'setSubroutineParent: address=' + DelayedLog . getNumber ( address ) + ': returns. memory already checked.' ) ; break ; } const label = this . labels . get ( address ) ; if ( label ) { if ( label != parentLabel ) { const type = label . type ; if ( type == NumberType . CODE_SUB || type == NumberType . CODE_RST || type == NumberType . CODE_LBL ) break ; } } const opcode = Opcode . getOpcodeAt ( this . memory , address ) ; opcodeClone = { ... opcode } ; this . addressParents [ address ] = parentLabel ; DelayedLog . log ( ( ) => 'setSubroutineParent: address=' + DelayedLog . getNumber ( address ) + ': added. ' + opcodeClone . name ) ; if ( opcodeClone . flags & OpcodeFlag . BRANCH_ADDRESS ) { const branchAddress = opcodeClone . value ; { DelayedLog . log ( ( ) => 'setSubroutineParent: address=' + DelayedLog . getNumber ( address ) + ': branching to ' + DelayedLog . getNumber ( branchAddress ) + '.\n' ) ; DelayedLog . pushTab ( ) ; this . setSubroutineParent ( branchAddress , parentLabel ) ; DelayedLog . popTab ( ) ; } } address += opcodeClone . length ; } while ( ! ( opcodeClone . flags & OpcodeFlag . STOP ) ) ; DelayedLog . log ( ( ) => 'setSubroutineParent: address=' + DelayedLog . getNumber ( address ) + ': stop.\n' ) ; } /**
     * Adds the 'calls'-list to the subroutine labels.
     * The reference list already includes the references (subroutines) who
     * call the label.
     * Now a list should be added to the label which contains all called
     * subroutines.
     * This is for call-graphs and for the comments in the listing.
     */ protected addCallsListToLabels ( ) { for ( let [ , label ] of this . labels ) { switch ( label . type ) { case NumberType . CODE_SUB : case NumberType . CODE_RST : case NumberType . CODE_LBL : const refs = label . references ; for ( const ref of refs ) { const parent = this . addressParents [ ref ] ; if ( parent ) { parent . calls . push ( label ) ; } } } } } /**
     * Calculates the statistics like size or cyclomatic complexity of all
     * subroutines.
     * Fills the 'subroutineStatistics' map.
     */ protected countStatistics ( ) { for ( let [ address , label ] of this . labels ) { if ( label . isEqu ) continue ; switch ( label . type ) { case NumberType . CODE_RST : if ( this . rstDontFollowAddresses . indexOf ( address ) >= 0 ) { const statistics = { sizeInBytes : 0 , countOfInstructions : 0 , CyclomaticComplexity : 0 } ; this . subroutineStatistics . set ( label , statistics ) ; break ; } case NumberType . CODE_SUB : case NumberType . CODE_LBL : const addresses = new Array < number > ( ) ; const statistics = this . countAddressStatistic ( address , addresses ) ; statistics . CyclomaticComplexity ++ ; this . subroutineStatistics . set ( label , statistics ) ; if ( statistics . sizeInBytes > this . statisticsMax . sizeInBytes ) this . statisticsMax . sizeInBytes = statistics . sizeInBytes ; if ( statistics . countOfInstructions > this . statisticsMax . countOfInstructions ) this . statisticsMax . countOfInstructions = statistics . countOfInstructions ; if ( statistics . CyclomaticComplexity > this . statisticsMax . CyclomaticComplexity ) this . statisticsMax . CyclomaticComplexity = statistics . CyclomaticComplexity ; if ( statistics . sizeInBytes < this . statisticsMin . sizeInBytes ) this . statisticsMin . sizeInBytes = statistics . sizeInBytes ; if ( statistics . countOfInstructions < this . statisticsMin . countOfInstructions ) this . statisticsMin . countOfInstructions = statistics . countOfInstructions ; if ( statistics . CyclomaticComplexity < this . statisticsMin . CyclomaticComplexity ) this . statisticsMin . CyclomaticComplexity = statistics . CyclomaticComplexity ; } } } param address param addresses returns protected countAddressStatistic ( address , addresses ) { let statistics = { sizeInBytes : 0 , countOfInstructions : 0 , CyclomaticComplexity : 0 } ; let opcodeClone ; do { const memAttr = this . memory . getAttributeAt ( address ) ; if ( ! ( memAttr & MemAttribute . ASSIGNED ) ) { return statistics ; } if ( addresses . indexOf ( address ) >= 0 ) return statistics ; addresses . push ( address ) ; const opcode = Opcode . getOpcodeAt ( this . memory , address ) ; opcodeClone = { ... opcode } ; statistics . sizeInBytes += opcodeClone . length ; statistics . countOfInstructions ++ ; if ( opcodeClone . flags & OpcodeFlag . BRANCH_ADDRESS ) { if ( opcode . name . indexOf ( ',' ) >= 0 ) statistics . CyclomaticComplexity ++ ; } else if ( ( opcodeClone . flags & OpcodeFlag . RET ) && ( opcodeClone . flags & OpcodeFlag . CONDITIONAL ) ) { statistics . CyclomaticComplexity ++ ; } if ( opcodeClone . flags & OpcodeFlag . BRANCH_ADDRESS ) { if ( ! ( opcodeClone . flags & OpcodeFlag . CALL ) ) { const branchAddress = opcodeClone . value ; const branchLabel = this . labels . get ( branchAddress ) ; let isSUB = false ; if ( branchLabel ) if ( branchLabel . type == NumberType . CODE_SUB || branchLabel . type == NumberType . CODE_RST ) isSUB = true ; if ( ! isSUB ) { const addStat = this . countAddressStatistic ( branchAddress , addresses ) ; statistics . sizeInBytes += addStat . sizeInBytes ; statistics . countOfInstructions += addStat . countOfInstructions ; statistics . CyclomaticComplexity += addStat . CyclomaticComplexity ; } } } address += opcodeClone . length ; const nextLabel = this . labels . get ( address ) ; if ( nextLabel ) { const type = nextLabel . type ; if ( type == NumberType . CODE_SUB || type == NumberType . CODE_RST ) break ; } } while ( ! ( opcodeClone . flags & OpcodeFlag . STOP ) ) ; return statistics ; } protected assignLabelNames ( ) { if ( this . funcAssignLabels ) { for ( const [ address , label ] of this . labels ) { label . name = this . funcAssignLabels ( address ) ; } return ; } const localLabels = new Map < DisLabel , Array < DisLabel > > ( ) ; const localLoops = new Map < DisLabel , Array < DisLabel > > ( ) ; let labelSubCount = 0 ; let labelLblCount = 0 ; let labelDataLblCount = 0 ; let labelSelfModifyingCount = 0 ; for ( const [ address , label ] of this . labels ) { const type = label . type ; switch ( type ) { case NumberType . CODE_SUB : labelSubCount ++ ; break ; case NumberType . CODE_LBL : labelLblCount ++ ; break ; case NumberType . DATA_LBL : const memAttr = this . memory . getAttributeAt ( address ) ; if ( memAttr & MemAttribute . CODE ) { labelSelfModifyingCount ++ ; } else { labelDataLblCount ++ ; } break ; case NumberType . CODE_LOCAL_LBL : case NumberType . CODE_LOCAL_LOOP : const parentLabel = this . addressParents [ address ] ; if ( parentLabel ) { const arr = ( type == NumberType . CODE_LOCAL_LBL ) ? localLabels : localLoops ; let labelsArray = arr . get ( parentLabel ) ; if ( ! labelsArray ) { labelsArray = new Array < DisLabel > ( ) ; arr . set ( parentLabel , labelsArray ) ; } labelsArray . push ( label ) ; } break ; } } const labelSubCountDigits = labelSubCount . toString ( ) . length ; const labelLblCountDigits = labelLblCount . toString ( ) . length ; const labelDataLblCountDigits = labelDataLblCount . toString ( ) . length ; const labelSelfModifyingCountDigits = labelSelfModifyingCount . toString ( ) . length ; let subIndex = 1 ; let lblIndex = 1 ; let dataLblIndex = 1 ; let dataSelfModifyingIndex = 1 ; for ( const [ address , label ] of this . labels ) { if ( label . name ) continue ; const type = label . type ; switch ( type ) { case NumberType . CODE_SUB : label . name = ( label . belongsToInterrupt ) ? this . labelIntrptPrefix : '' + this . labelSubPrefix + this . getIndex ( subIndex , labelSubCountDigits ) ; subIndex ++ ; break ; case NumberType . CODE_LBL : label . name = ( label . belongsToInterrupt ) ? this . labelIntrptPrefix : '' + this . labelLblPrefix + this . getIndex ( lblIndex , labelLblCountDigits ) ; lblIndex ++ ; break ; case NumberType . CODE_RST : label . name = this . labelRstPrefix + Format . getHexString ( address , 2 ) ; break ; case NumberType . DATA_LBL : const memAttr = this . memory . getAttributeAt ( address ) ; if ( memAttr & MemAttribute . CODE ) { assert ( memAttr & MemAttribute . CODE_FIRST , 'assignLabelNames 2' ) ; label . name = this . labelSelfModifyingPrefix + this . getIndex ( dataSelfModifyingIndex , labelSelfModifyingCountDigits ) ; dataSelfModifyingIndex ++ ; } else { label . name = this . labelDataLblPrefix + this . getIndex ( dataLblIndex , labelDataLblCountDigits ) ; dataLblIndex ++ ; } break ; } } for ( const [ parentLabel , childLabels ] of localLabels ) { const localPrefix = parentLabel . name . toLowerCase ( ) ; const count = childLabels . length ; const digitCount = count . toString ( ) . length ; let index = 1 ; for ( let child of childLabels ) { const indexString = this . getIndex ( index , digitCount ) ; child . name = '.' + localPrefix + this . labelLocalLablePrefix ; if ( count > 1 ) child . name += indexString ; index ++ ; } } for ( let [ parentLabel , childLabels ] of localLoops ) { const localPrefix = parentLabel . name . toLowerCase ( ) ; const count = childLabels . length ; const digitCount = count . toString ( ) . length ; let index = 1 ; for ( let child of childLabels ) { const indexString = this . getIndex ( index , digitCount ) ; child . name = '.' + localPrefix + this . labelLoopPrefix ; if ( count > 1 ) child . name += indexString ; index ++ ; } } } /**
     * Add/create the comments for the labels.
     * If a comment already exists (e.g. set by the user) it is not overwritten.
     */ protected addLabelComments ( ) { for ( const [ address , label ] of this . labels ) { const type = label . type ; if ( type != NumberType . CODE_SUB && type != NumberType . CODE_RST && type != NumberType . CODE_LBL && type != NumberType . DATA_LBL ) continue ; let comment = this . addressComments . get ( address ) ; if ( comment ) continue ; comment = this . getAddressComment ( address ) ; if ( comment ) this . addressComments . set ( address , comment ) ; } } param address protected getAddressComment ( address ) { if ( this . disableCommentsInDisassembly ) return undefined ; let comments = this . addressComments . get ( address ) ; if ( comments ) return comments ; return this . getLabelComments ( address ) ; } param address return protected getLabelComments ( address ) { const addrLabel = this . labels . get ( address ) ; if ( ! addrLabel ) return undefined ; const lineArray = new Array < string > ( ) ; const refCount = addrLabel . references . size ; let line1 ; let line2 ; let line3 ; const type = addrLabel . type ; let name ; switch ( type ) { case NumberType . CODE_SUB : name = 'Subroutine' ; break ; case NumberType . CODE_RST : name = 'Restart' ; break ; case NumberType . DATA_LBL : name = 'Data' ; break ; default : name = 'Label' ; break ; } switch ( type ) { case NumberType . CODE_SUB : case NumberType . CODE_RST : { line2 = 'Called by: ' ; let first = true ; let recursiveFunction = false ; for ( const ref of addrLabel . references ) { if ( ! first ) line2 += ', ' ; const s = Format . getHexString ( ref , 4 ) + 'h' ; const parent = this . addressParents [ ref ] ; if ( parent ) { let parName ; if ( parent == addrLabel ) { parName = 'self' ; recursiveFunction = true ; } else parName = parent . name ; line2 += parName + '[' + s + ']' ; } else line2 += s ; first = false ; } line2 += ( addrLabel . references . size > 0 ) ? '.' : '-' ; line1 = name ; const stat = this . subroutineStatistics . get ( addrLabel ) ; if ( stat ) line1 += ': ' + ( ( recursiveFunction ) ? 'Recursive, ' : '' ) + 'Size=' + stat . sizeInBytes + ', CC=' + stat . CyclomaticComplexity + '.' ; else line1 += '.' ; lineArray . push ( line1 ) ; lineArray . push ( line2 ) ; if ( ! addrLabel . isEqu ) { line3 = 'Calls: ' ; first = true ; const callees = new Set < DisLabel > ( ) ; for ( const callee of addrLabel . calls ) { callees . add ( callee ) ; } line3 += Array . from ( callees ) . map ( refLabel => refLabel . name ) . join ( ', ' ) ; line3 += ( callees . size > 0 ) ? '.' : '-' ; lineArray . push ( line3 ) ; } break ; } default : { line1 = name ; line1 += ( refCount > 0 ) ? ' accessed by:' : ' not accessed.' ; lineArray . push ( line1 ) ; if ( refCount > 0 ) { const refArray = [ ... addrLabel . references ] . map ( addr => { let s = Format . getHexString ( addr , 4 ) + 'h' ; const parentLabel = this . addressParents [ addr ] ; if ( parentLabel ) { s += '(in ' + parentLabel . name + ')' ; } return s ; } ) ; const line2 = refArray . join ( ', ' ) ; lineArray . push ( line2 ) ; } break ; } } let comment = new Comment ( ) ; if ( addrLabel . isEqu ) { comment . inlineComment = '; ' + address . toString ( ) + '. ' + lineArray . join ( ' ' ) ; } else { comment . linesBefore = lineArray . map ( s => '; ' + s ) ; } return comment ; } param commentsFile public setAddressComments ( commentsFile ) { enum State { LinesBefore , lineOn , LinesAfter } ; const commentsLines = readFileSync ( commentsFile ) . toString ( ) . split ( '\n' ) ; commentsLines . push ( '' ) ; let state = State . LinesBefore ; let comment = new Comment ( ) ; let commentAddr ; let commentLabelName ; let lineNr = 0 ; for ( const line of commentsLines ) { lineNr ++ ; const match = /^\s*([^;]+)?(;.*)?(\S+)?/ . exec ( line ) ! ; assert ( match ) ; const addressPart = match [ 1 ] ; const commentPart = match [ 2 ] ; const misc = match [ 3 ] ; if ( misc ) { throw Error ( 'Unexpected line: "' + line + '", ' + commentsFile + ':' + lineNr + '.' ) ; } if ( ! addressPart && ! commentPart ) { if ( commentAddr != undefined ) { this . addressComments . set ( commentAddr , comment ) ; if ( commentLabelName ) { this . setLabel ( commentAddr , commentLabelName , NumberType . DATA_LBL ) ; } } state = State . LinesBefore ; comment = new Comment ( ) ; commentAddr = undefined ; commentLabelName = undefined ; continue ; } if ( addressPart ) { if ( state == State . LinesBefore ) { state = State . lineOn ; } } switch ( state ) { case State . LinesBefore : comment . addBefore ( commentPart ) ; break ; case State . lineOn : comment . inlineComment = commentPart ; const match2 = /^(0x)?([0-9a-f]+)(\s+([\w\.]+))?/i . exec ( addressPart ) ; if ( match2 ) { const addrString = match2 [ 2 ] ; commentAddr = parseInt ( addrString , 16 ) ; commentLabelName = match2 [ 4 ] ; state = State . LinesAfter ; } else { throw Error ( 'Expected an address: "' + line + '", ' + commentsFile + ':' + lineNr + '.' ) ; } break ; case State . LinesAfter : if ( addressPart ) { throw Error ( 'Expected "after"-comment or newline: "' + line + '", ' + commentsFile + ':' + lineNr + '.' ) ; } comment . addAfter ( match [ 2 ] ) ; break ; } } } /**
     * Returns all (main) labels with address and comments.
     */ public getMainLabels ( ) { let text = '' ; for ( let [ address , label ] of this . labels ) { const type = label . type ; if ( type != NumberType . CODE_SUB && type != NumberType . CODE_LBL && type != NumberType . CODE_RST && type != NumberType . DATA_LBL ) continue ; const addrLabelLine = Format . getHexString ( address , 4 ) + '\t' + label . name ; const comment = this . getAddressComment ( address ) ; const commentLines = Comment . getLines ( comment , addrLabelLine , this . disableCommentsInDisassembly ) ; text += commentLines . join ( '\n' ) ; text += '\n\n' ; } return text ; } returns protected disassembleMemory ( ) { let lines = new Array < string > ( ) ; if ( this . labels . size == 0 ) return lines ; let address = - 1 ; for ( const [ addr , label ] of this . labels ) { if ( label . isEqu ) continue ; if ( address == - 1 ) { this . addEmptyLines ( lines ) ; let orgLine = ' ' . repeat ( this . clmnsBytes ) + this . rightCase ( 'ORG ' ) + Format . getHexString ( addr ) + 'h' ; if ( ! this . disableCommentsInDisassembly ) orgLine += '; ' + Format . getConversionForAddress ( addr ) ; lines . push ( orgLine ) ; } else { const unassignedSize = addr - address ; if ( unassignedSize < 0 ) continue ; this . addEmptyLines ( lines ) ; let orgLine = ' ' . repeat ( this . clmnsBytes ) + this . rightCase ( 'ORG ' ) + Format . getHexString ( addr ) + 'h' ; if ( ! this . disableCommentsInDisassembly ) orgLine += '; ' + Format . getConversionForAddress ( addr ) ; lines . push ( orgLine ) ; } address = addr ; let prevMemoryAttribute = MemAttribute . DATA ; let prevParent ; while ( true ) { let attr = this . memory . getAttributeAt ( address ) ; if ( ! ( attr & MemAttribute . ASSIGNED ) ) { break ; } this . resetAddEmptyLine ( ) ; const parent = this . addressParents [ address ] ; if ( parent != prevParent ) { this . addEmptyLines ( lines ) ; prevParent = parent ; } const addrLabel = this . labels . get ( address ) ; const comment = this . addressComments . get ( address ) ; if ( addrLabel ) { const type = addrLabel . type ; if ( type == NumberType . CODE_SUB || type == NumberType . CODE_LBL || type == NumberType . DATA_LBL || type == NumberType . CODE_RST ) { this . addEmptyLines ( lines ) ; } let labelLine = addrLabel . name + ':' ; if ( this . clmnsAddress > 0 ) { labelLine = Format . addSpaces ( Format . getHexString ( address ) , this . clmnsAddress ) + labelLine ; if ( this . DBG_ADD_DEC_ADDRESS ) { labelLine = Format . addSpaces ( address . toString ( ) , 5 ) + ' ' + labelLine ; } } const commentLines = Comment . getLines ( comment , labelLine , this . disableCommentsInDisassembly ) ; lines . push ( ... commentLines ) ; } let addAddress ; let line ; let commentText ; if ( attr & MemAttribute . CODE ) { const opcode = Opcode . getOpcodeAt ( this . memory , address ) ; const opCodeDescription = opcode . disassemble ( this . memory ) ; line = this . formatDisassembly ( address , opcode . length , opCodeDescription . mnemonic ) ; commentText = opCodeDescription . comment ; addAddress = opcode . length ; } else { if ( ! ( prevMemoryAttribute & MemAttribute . DATA ) ) this . addEmptyLines ( lines ) ; attr |= MemAttribute . DATA ; let memValue = this . memory . getValueAt ( address ) ; let mainString = this . rightCase ( 'DEFB ' ) + Format . getHexString ( memValue , 2 ) + 'h' ; commentText = Format . getVariousConversionsForByte ( memValue ) ; line = this . formatDisassembly ( address , 1 , mainString ) ; addAddress = 1 ; } if ( this . DBG_ADD_DEC_ADDRESS ) { line = Format . addSpaces ( address . toString ( ) , 5 ) + ' ' + line ; } if ( ! comment || addrLabel ) { if ( ! this . disableCommentsInDisassembly && commentText && commentText . length > 0 ) line += '\t; ' + commentText ; lines . push ( line ) ; } else { const commentLines = Comment . getLines ( comment , line , this . disableCommentsInDisassembly ) ; lines . push ( ... commentLines ) ; } address += addAddress ; let attrEnd = this . memory . getAttributeAt ( address ) ; if ( address < 0x10000 ) { if ( ! this . disableCommentsInDisassembly && ! ( attrEnd & MemAttribute . ASSIGNED ) ) { lines . push ( '; ...' ) ; lines . push ( '; ...' ) ; lines . push ( '; ...' ) ; } } prevMemoryAttribute = attr ; } } return lines ; } param address param size param mainString protected formatDisassembly ( address , size , mainString ) { const memory = ( this . addOpcodeBytes ) ? this . memory : undefined ; return Format . formatDisassembly ( memory , this . opcodesLowerCase , this . clmnsAddress , this . clmnsBytes , this . clmnsOpcodeFirstPart , this . clmsnOpcodeTotal , address , size , mainString ) ; } param index param countDigits protected getIndex ( index , countDigits ) { const str = index . toString ( ) ; return '0' . repeat ( countDigits - str . length ) + str ; } param lines protected addEmptyLines ( lines ) { if ( this . emptyLinesWritten ) return ; for ( let i = 0 ; i < this . numberOfLinesBetweenBlocks ; i ++ ) { lines . push ( '' ) ; } this . emptyLinesWritten = true ; } /**
     * Enables writing empyty lines.
     * Used to prohibit that empty lines are written more than once.
     */ protected resetAddEmptyLine ( ) { this . emptyLinesWritten = false ; } param s returns protected rightCase ( s ) { if ( this . opcodesLowerCase ) return s . toLowerCase ( ) ; return s ; } /**
     * Creates a reverted map from the this.labels map (<address, DisLabel>).
     * The created map consists of key-value pairs <name,address>
     * and is stored in this.revertedLabelMap.
     */ public createRevertedLabelMap ( ) { this . revertedLabelMap = new Map < string , number > ( ) ; for ( const [ address , label ] of this . labels ) { this . revertedLabelMap . set ( label . getName ( ) , address ) ; } } /**
     * Returns a map of chosen addresses, labels for creating the
     * dot graph.
     */ public getGraphLabels ( addrString , chosenLabels ? ) { if ( ! chosenLabels ) chosenLabels = new Map < number , DisLabel > ( ) ; let addr ; if ( typeof ( addrString ) == 'string' ) { addr = this . revertedLabelMap . get ( addrString ) ; if ( ! addr ) throw Error ( 'Could not find "' + addrString + '" while creating graph.' ) ; } else { addr = addrString ; } const label = this . labels . get ( addr ) ; if ( ! label ) throw Error ( 'Could not find address for "' + addrString + '" while creating graph.' ) ; if ( ! chosenLabels . get ( addr ) ) { chosenLabels . set ( addr , label ) ; for ( const called of label . calls ) { const callee = called . getName ( ) ; this . getGraphLabels ( callee , chosenLabels ) ; } } return chosenLabels ; } param labels param name returns public getCallGraph ( labels ) { const rankSame1 = new Array < string > ( ) ; const rankSame2 = new Array < string > ( ) ; let text = 'digraph Callgraph {\n\n' ; text += this . dotFormatString + '\n' ; const fontSizeMin = 13 ; const fontSizeMax = 40 ; const min = this . statisticsMin . CyclomaticComplexity ; const complDiff = this . statisticsMax . CyclomaticComplexity - min ; const fontSizeFactor = ( fontSizeMax - fontSizeMin ) / ( ( complDiff < 8 ) ? 8 : complDiff ) ; for ( const [ address , label ] of labels ) { const type = label . type ; if ( type != NumberType . CODE_SUB && type != NumberType . CODE_LBL && type != NumberType . CODE_RST ) continue ; let colorString = this . dotMarkedLabels . get ( address ) ; if ( ! colorString ) { colorString = this . dotMarkedLabels . get ( label . getName ( ) ) ; } if ( label . isEqu ) { if ( ! colorString ) colorString = 'lightgray' ; text += label . name + ' [fontsize="' + fontSizeMin + '"];\n' ; const nodeName = this . nodeFormat ( label . name , label . id , address , undefined , undefined , undefined ) ; text += label . name + ' [label="' + nodeName + '"];\n' ; } else { const stats = this . subroutineStatistics . get ( label ) ! ; assert ( stats ) ; const fontSize = fontSizeMin + fontSizeFactor * ( stats . CyclomaticComplexity - min ) ; text += '"' + label . name + '" [fontsize="' + Math . round ( fontSize ) + '"];\n' ; const nodeName = this . nodeFormat ( label . name , label . id , address , stats . CyclomaticComplexity , stats . sizeInBytes , stats . countOfInstructions ) ; text += '"' + label . name + '" [label="' + nodeName + '"];\n' ; const callees = new Set < DisLabel > ( ) ; for ( const callee of label . calls ) { callees . add ( callee ) ; } if ( label . references . size == 0 || label . type == NumberType . CODE_LBL ) { if ( ! colorString ) colorString = 'lightyellow' ; if ( label . references . size == 0 ) rankSame1 . push ( label . name ) ; else rankSame2 . push ( label . name ) ; } if ( callees . size > 0 ) text += '"' + label . name + '" -> { ' + Array . from ( callees ) . map ( refLabel => '"' + refLabel . name + '"' ) . join ( ' ' ) + ' };\n' ; } if ( colorString ) text += '"' + label . name + '" [fillcolor=' + colorString + ', style=filled];\n' ; } if ( rankSame1 . length > 0 ) text += '\n{ rank=same; "' + rankSame1 . join ( '", "' ) + '" };\n\n' ; if ( rankSame2 . length > 0 ) text += '\n{ rank=same; "' + rankSame2 . join ( '", "' ) + '" };\n\n' ; text += '}\n' ; return text ; } param labelName param labelId param address param cc param size param instructions protected nodeFormat ( labelName , labelId , address , cc ? , size ? , instructions ? ) { let text = this . nodeFormatString ; try { text = text . replace ( /\${label}/g , labelName ) ; text = text . replace ( /\${id}/g , labelId . toString ( ) ) ; text = text . replace ( /\${address}/g , Format . getHexString ( address , 4 ) ) ; text = text . replace ( /\${CC}/g , ( cc ) ? cc . toString ( ) : '?' ) ; text = text . replace ( /\${size}/g , ( size ) ? size . toString ( ) : '?' ) ; text = text . replace ( /\${instructions}/g , ( instructions ) ? instructions . toString ( ) : '?' ) ; } catch ( e ) { throw Error ( 'Dot format string wrong: ' + e ) ; } return text ; } param addr param colorString public setDotHighlightAddress ( addr , colorString ) { this . dotMarkedLabels . set ( addr , colorString ) ; } param startAddresses public getFlowChart ( startAddresses ) { let text = 'digraph FlowChart {\n\n' ; text += 'node [shape=box];\n' ; for ( const startAddress of startAddresses ) { const label = this . labels . get ( startAddress ) ; const addressString = Format . getHexString ( startAddress , 4 ) ; let name ; if ( label ) name = label . name ; if ( ! name ) name += '0x' + addressString ; const start = 'b' + addressString + 'start' ; text += start + ' [label="' + name + '", fillcolor=lightgray, style=filled, shape=tab];\n' ; text += start + ' -> b' + Format . getHexString ( startAddress , 4 ) + ';\n' ; const end = 'b' + addressString + 'end' ; text += end + ' [label="end", shape=doublecircle];\n' ; const addrsArray = new Array < number > ( ) ; this . getSubroutineAddresses ( startAddress , addrsArray ) ; this . reduceSubroutineAddresses ( startAddress , addrsArray ) ; const processedAddrsArray = new Array < number > ( ) ; const bText = this . getBranchForAddress ( startAddress , addrsArray , processedAddrsArray ) ; text += bText ; } text += '\n}\n' ; return text ; } param address param addrsArray param processedAddrsArray protected getBranchForAddress ( address , addrsArray , processedAddrsArray ) { const branch = 'b' + Format . getHexString ( address , 4 ) ; let text = branch + ' [label="' ; let disTexts = [ ] ; let opcode ; do { processedAddrsArray . push ( address ) ; opcode = Opcode . getOpcodeAt ( this . memory , address ) ; const disText = opcode . disassemble ( ) ; disTexts . push ( disText . mnemonic ) ; address += opcode . length ; if ( this . labels . get ( address ) ) break ; } while ( ! ( opcode . flags & OpcodeFlag . BRANCH_ADDRESS ) && ! ( opcode . flags & OpcodeFlag . RET ) && ! ( opcode . flags & OpcodeFlag . STOP ) ) ; text += disTexts . join ( '\\l' ) + '\\l' ; text += '"];\n' ; if ( opcode . flags & OpcodeFlag . BRANCH_ADDRESS ) { const branchAddress = opcode . value ; if ( addrsArray . indexOf ( branchAddress ) >= 0 ) { text += branch + ' -> b' + Format . getHexString ( branchAddress , 4 ) + ' [headport="n", tailport="e"];\n' ; if ( processedAddrsArray . indexOf ( branchAddress ) < 0 ) { const textBranch = this . getBranchForAddress ( branchAddress , addrsArray , processedAddrsArray ) ; text += textBranch ; } } } if ( ! ( opcode . flags & OpcodeFlag . STOP ) ) { if ( addrsArray . indexOf ( address ) >= 0 ) { text += branch + ' -> b' + Format . getHexString ( address , 4 ) + ';\n' ; if ( processedAddrsArray . indexOf ( address ) < 0 ) { const textBranch = this . getBranchForAddress ( address , addrsArray , processedAddrsArray ) ; text += textBranch ; } } } if ( opcode . flags & ( OpcodeFlag . STOP | OpcodeFlag . RET ) ) { assert ( addrsArray . length > 0 ) ; text += branch + ' -> b' + Format . getHexString ( addrsArray [ 0 ] , 4 ) + 'end;\n' ; } return text ; } }