def test_invalid_lines(self): ctl_specs = [ (' 30000,1', 'blank directive with no containing block'), ('B 30745,15,5:X10', 'invalid integer'), ('T 30760,5,2:Y3', 'invalid integer'), ('W 30765,5,1:B4', 'invalid integer'), ('S 30770,10,T8:2', 'invalid integer'), ('C 40000,Q', 'invalid integer'), ('; @label:EDCBA=Z', 'invalid ASM directive address'), ('; @label=Z', 'invalid ASM directive declaration'), ('b 50000,20', 'extra parameters after address'), ('d 50020', 'invalid directive'), ('! 50030', 'invalid directive') ] ctls = [spec[0] for spec in ctl_specs] ctl_parser = CtlParser() ctlfile = self.write_text_file('\n'.join(ctls)) ctl_parser.parse_ctl(ctlfile) exp_warnings = [] for line_no, (ctl, error_msg) in enumerate(ctl_specs, 1): if error_msg: exp_warnings.append('WARNING: Ignoring line {} in {} ({}):'.format(line_no, ctlfile, error_msg)) warnings = self.err.getvalue().split('\n')[:-1] self.assertEqual(exp_warnings, warnings[0::2]) invalid_ctls = [spec[0] for spec in ctl_specs if spec[1]] self.assertEqual(invalid_ctls, warnings[1::2])
def test_word_formats(self): ctl = '\n'.join(( 'w 40000 Test word formats', ' 40000,10 5 default', ' 40010,b10 5 words in binary format', 'W 40020,b10,6,d2,h2 3 binary, 1 decimal, 1 hex', 'W 40030,b10,4:d4:h2 2 binary, 2 decimal, 1 hex, one line', ' 40040,10,b2,4,h4 1 binary, 2 default, 2 hex', ' 40050,10,b2:6:h2 1 binary, 3 default, 1 hex, one line', )) ctl_parser = CtlParser() ctlfile = self.write_text_file(ctl) ctl_parser.parse_ctl(ctlfile) exp_lengths = { 40010: [(None, [(None, 'b')])], 40020: [ (6, [(6, 'b')]), (2, [(2, 'd')]), (2, [(2, 'h')]) ], 40030: [(10, [(4, 'b'), (4, 'd'), (2, 'h')])], 40040: [ (2, [(2, 'b')]), (4, None), (4, [(4, 'h')]) ], 40050: [(10, [(2, 'b'), (6, None), (2, 'h')])] } self.assertEqual(exp_lengths, ctl_parser.lengths)
def test_word_formats_hex(self): snapshot = [240] * 8 ctl = '\n'.join(( 'w 00000', ' 00000,8,b2,d2,h2,2', 'i 00008' )) ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) disassembly = Disassembly(snapshot, ctl_parser, True, asm_hex=True) entries = disassembly.entries self.assertEqual(len(entries), 2) entry = entries[0] self.assertEqual(entry.address, 0) instructions = entry.instructions actual_instructions = [(i.address, i.operation) for i in instructions] exp_instructions = [ (0, 'DEFW %1111000011110000'), (2, 'DEFW 61680'), (4, 'DEFW $F0F0'), (6, 'DEFW $F0F0') ] self.assertEqual(exp_instructions, actual_instructions)
def test_s_directives(self): snapshot = [] ctl = '\n'.join(( 's 00000', ' 00000,4', ' 00004,b4', 'S 00008,d4', ' 00012,h8', ' 00020,40,b10,10,h10', 'S 00060,b40,10,d10,h10', ' 00100,d40,b10,10,h10', ' 00140,h60,b10,d10,40', 'S 00200,768,b256,d256,h256', ' 00968,56,16:b%10101010,40:h17', 'i 01024' )) ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) disassembly = Disassembly(snapshot, ctl_parser, True) entries = disassembly.entries self.assertEqual(len(entries), 2) entry = entries[0] self.assertEqual(entry.address, 0) instructions = entry.instructions actual_instructions = [(i.address, i.operation) for i in instructions] exp_instructions = [ ( 0, 'DEFS 4'), ( 4, 'DEFS %00000100'), ( 8, 'DEFS 4'), ( 12, 'DEFS 8'), ( 20, 'DEFS %00001010'), ( 30, 'DEFS 10'), ( 40, 'DEFS $0A'), ( 50, 'DEFS $0A'), ( 60, 'DEFS %00001010'), ( 70, 'DEFS 10'), ( 80, 'DEFS $0A'), ( 90, 'DEFS $0A'), (100, 'DEFS %00001010'), (110, 'DEFS 10'), (120, 'DEFS $0A'), (130, 'DEFS $0A'), (140, 'DEFS %00001010'), (150, 'DEFS 10'), (160, 'DEFS $28'), (200, 'DEFS %0000000100000000'), (456, 'DEFS 256'), (712, 'DEFS $0100'), (968, 'DEFS 16,%10101010'), (984, 'DEFS 40,$11') ] self.assertEqual(exp_instructions, actual_instructions)
def test_byte_formats(self): snapshot = [42] * 75 ctl = '\n'.join(( 'b 00000', ' 00000,b5', ' 00005,b15', ' 00020,b5,2,d2,h1', 'B 00025,b5,2:d2:h1', ' 00030,h10,5:d3:b2', 'B 00040,5,b1,h2', ' 00045,5,h1,T4', ' 00050,5,b2:T3', 'T 00055,5,h2,3', 'T 00060,5,2:d3', 'T 00065,5,3,B1', 'T 00070,5,B2:h3', 'i 00075' )) ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) disassembly = Disassembly(snapshot, ctl_parser, True) entries = disassembly.entries self.assertEqual(len(entries), 2) entry = entries[0] self.assertEqual(entry.address, 0) instructions = entry.instructions actual_instructions = [(i.address, i.operation) for i in instructions] exp_instructions = [ ( 0, 'DEFB %00101010,%00101010,%00101010,%00101010,%00101010'), ( 5, 'DEFB %00101010,%00101010,%00101010,%00101010,%00101010,%00101010,%00101010,%00101010'), (13, 'DEFB %00101010,%00101010,%00101010,%00101010,%00101010,%00101010,%00101010'), (20, 'DEFB %00101010,%00101010'), (22, 'DEFB 42,42'), (24, 'DEFB $2A'), (25, 'DEFB %00101010,%00101010,42,42,$2A'), (30, 'DEFB $2A,$2A,$2A,$2A,$2A,42,42,42,%00101010,%00101010'), (40, 'DEFB %00101010'), (41, 'DEFB $2A,$2A'), (43, 'DEFB $2A,$2A'), (45, 'DEFB $2A'), (46, 'DEFB "****"'), (50, 'DEFB %00101010,%00101010,"***"'), (55, 'DEFM $2A,$2A'), (57, 'DEFM "***"'), (60, 'DEFM "**",42,42,42'), (65, 'DEFM "***"'), (68, 'DEFM 42'), (69, 'DEFM 42'), (70, 'DEFM 42,42,$2A,$2A,$2A') ] self.assertEqual(actual_instructions, exp_instructions)
def test_s_directives(self): ctl = '\n'.join(( 's 50000 Test s/S directives', ' 50000,10', ' 50010,b10', ' 50020,d10', ' 50030,h10', 'S 50040,b20,5,d5,h5', 'S 50060,d20,b5,5,h5', 'S 50080,h20,b5,d5,5', ' 50100,20,b5,d5,5', ' 50120,20,d20:b%10001000', ' 50140,20,20:h$44', ' 50160,12,10:h10,h2:2' )) ctl_parser = CtlParser() ctlfile = self.write_text_file(ctl) ctl_parser.parse_ctl(ctlfile) exp_lengths = { 50010: [(None, [(None, 'b')])], 50020: [(None, [(None, 'd')])], 50030: [(None, [(None, 'h')])], 50040: [ (5, [(5, 'b')]), (5, [(5, 'd')]), (5, [(5, 'h')]) ], 50060: [ (5, [(5, 'b')]), (5, [(5, 'd')]), (5, [(5, 'h')]) ], 50080: [ (5, [(5, 'b')]), (5, [(5, 'd')]), (5, [(5, 'h')]) ], 50100: [ (5, [(5, 'b')]), (5, [(5, 'd')]), (5, None) ], 50120: [(20, [(20, 'd'), (136, 'b')])], 50140: [(20, [(20, None), (68, 'h')])], 50160: [ (10, [(10, None), (10, 'h')]), (2, [(2, 'h'), (2, None)]), ] } self.assertEqual(exp_lengths, ctl_parser.lengths)
def test_comments(self): ctl = '\n'.join(( '# This is a comment', 'b 32768', '% This is also a comment', 'w 32769', '; This is a comment too' )) ctl_parser = CtlParser() ctlfile = self.write_text_file(ctl) ctl_parser.parse_ctl(ctlfile) self.assertEqual(self.err.getvalue(), '') self.assertEqual({32768: 'b', 32769: 'w'}, ctl_parser.ctls)
def test_word_formats(self): snapshot = [170, 53] * 32 ctl = '\n'.join(( 'w 00000', ' 00000,4', ' 00004,b4', ' 00008,d4', 'W 00012,h4', 'W 00016,b8,2,d2,h4', ' 00024,d8,b4:2:h2', ' 00032,h8,b2:d4:2', ' 00040,8,b2,4,h2', 'W 00048,8,b2:2:h4', ' 00056,8,4', 'i 00064' )) ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) disassembly = Disassembly(snapshot, ctl_parser, True) entries = disassembly.entries self.assertEqual(len(entries), 2) entry = entries[0] self.assertEqual(entry.address, 0) instructions = entry.instructions actual_instructions = [(i.address, i.operation) for i in instructions] exp_instructions = [ ( 0, 'DEFW 13738'), ( 2, 'DEFW 13738'), ( 4, 'DEFW %0011010110101010'), ( 6, 'DEFW %0011010110101010'), ( 8, 'DEFW 13738'), (10, 'DEFW 13738'), (12, 'DEFW $35AA'), (14, 'DEFW $35AA'), (16, 'DEFW %0011010110101010'), (18, 'DEFW 13738'), (20, 'DEFW $35AA,$35AA'), (24, 'DEFW %0011010110101010,%0011010110101010,13738,$35AA'), (32, 'DEFW %0011010110101010,13738,13738,$35AA'), (40, 'DEFW %0011010110101010'), (42, 'DEFW 13738,13738'), (46, 'DEFW $35AA'), (48, 'DEFW %0011010110101010,13738,$35AA,$35AA'), (56, 'DEFW 13738,13738'), (60, 'DEFW 13738,13738') ] self.assertEqual(actual_instructions, exp_instructions)
def test_byte_formats(self): ctl = '\n'.join(( 'b 40000 Test byte formats', ' 40000,b10 10 bytes in binary format', 'B 40010,b10,5,d3,h2 5 binary, 3 decimal, 2 hex', 'B 40020,b10,2:d3:h5 2 binary, 3 decimal, 5 hex, one line', ' 40030,10,b6,3,h1 6 binary, 3 default, 1 hex', ' 40040,10,b5:2:h3 5 binary, 2 default, 3 hex, one line', ' 40050,10,1,T9 1 default, 9 text', ' 40060,10,h4:T6 4 hex, 6 text, one line', 'T 40070,10,3,b7 3 text, 7 binary', 'T 40080,10,2:h8 2 text, 8 hex, one line', 'T 40090,10,5,B5 5 text, 5 default' )) ctl_parser = CtlParser() ctlfile = self.write_text_file(ctl) ctl_parser.parse_ctl(ctlfile) exp_lengths = { 40000: [(None, [(None, 'b')])], 40010: [ (5, [(5, 'b')]), (3, [(3, 'd')]), (2, [(2, 'h')]) ], 40020: [(10, [(2, 'b'), (3, 'd'), (5, 'h')])], 40030: [ (6, [(6, 'b')]), (3, None), (1, [(1, 'h')]) ], 40040: [(10, [(5, 'b'), (2, None), (3, 'h')])], 40050: [ (1, None), (9, [(9, 'T')]) ], 40060: [(10, [(4, 'h'), (6, 'T')])], 40070: [ (3, None), (7, [(7, 'b')]) ], 40080: [(10, [(2, None), (8, 'h')])], 40090: [ (5, None), (5, [(5, 'B')]) ], } self.assertEqual(exp_lengths, ctl_parser.lengths)
def test_invalid_lines(self): ctl_specs = [ (' 30000,1', 'blank directive with no containing block'), ('B 30745,15,5:X10', 'invalid integer'), ('T 30760,5,2:Y3', 'invalid integer'), ('W 30765,5,1:B4', 'invalid integer'), ('S 30770,10,T8:2', 'invalid integer'), ('B 30780,10,h,5', 'invalid integer'), ('C 40000,Q', 'invalid integer'), ('@ FEDCB label=Z', 'invalid ASM directive address'), ('@ 49152', 'invalid ASM directive declaration'), ('b 50000,20', 'extra parameters after address'), ('c 50000,20', 'extra parameters after address'), ('g 50000,20', 'extra parameters after address'), ('i 50000,20', 'extra parameters after address'), ('s 50000,20', 'extra parameters after address'), ('t 50000,20', 'extra parameters after address'), ('u 50000,20', 'extra parameters after address'), ('w 50000,20', 'extra parameters after address'), ('D 50000,20 Desc.', 'extra parameters after address'), ('E 50000,20 End.', 'extra parameters after address'), ('N 50000,20 Note.', 'extra parameters after address'), ('R 50000,20 A 10', 'extra parameters after address'), ('b b50010', 'invalid address'), ('d 50020', 'invalid directive'), ('! 50030', 'invalid directive'), ('@ 50000 ignoreua:g', "invalid @ignoreua directive suffix: 'g'"), ('L 51000', 'loop length not specified'), ('L 51000,10', 'loop count not specified') ] ctls = [spec[0] for spec in ctl_specs] ctl_parser = CtlParser() ctlfile = self.write_text_file('\n'.join(ctls)) ctl_parser.parse_ctl(ctlfile) exp_warnings = [] for line_no, (ctl, error_msg) in enumerate(ctl_specs, 1): if error_msg: exp_warnings.append('WARNING: Ignoring line {} in {} ({}):'.format(line_no, ctlfile, error_msg)) warnings = self.err.getvalue().split('\n')[:-1] self.assertEqual(exp_warnings, warnings[0::2]) invalid_ctls = [spec[0] for spec in ctl_specs if spec[1]] self.assertEqual(invalid_ctls, warnings[1::2])
def test_byte_formats_hex(self): snapshot = [85] * 4 ctl = '\n'.join(( 'b 00000', ' 00000,4,b1:d1:h1:1', 'i 00004' )) ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) disassembly = Disassembly(snapshot, ctl_parser, True, asm_hex=True) entries = disassembly.entries self.assertEqual(len(entries), 2) entry = entries[0] self.assertEqual(entry.address, 0) instructions = entry.instructions self.assertEqual(len(instructions), 1) defb = instructions[0] self.assertEqual(defb.operation, 'DEFB %01010101,85,$55,$55')
def run(snafile, options): # Read the snapshot file if snafile[-4:].lower() in ('.sna', '.szx', '.z80'): snapshot = get_snapshot(snafile, options.page) start = max(START, options.start) else: ram = read_bin_file(snafile) if options.org is None: org = 65536 - len(ram) else: org = options.org snapshot = [0] * org snapshot.extend(ram) start = max(org, options.start) end = min(options.end, len(snapshot)) # Pad out the end of the snapshot to avoid disassembly errors when an # instruction crosses the 64K boundary snapshot += [0] * (65539 - len(snapshot)) if options.sftfile: # Use a skool file template info('Using skool file template: {}'.format(options.sftfile)) writer = SftParser(snapshot, options.sftfile, options.zfill, options.asm_hex, options.asm_lower) writer.write_skool(options.start, options.end) return if options.genctlfile: # Generate a control file ctls = generate_ctls(snapshot, start, end, options.code_map) write_ctl(options.genctlfile, ctls, options.ctl_hex) ctl_parser = CtlParser(ctls) elif options.ctlfile: # Use a control file info('Using control file: {}'.format(options.ctlfile)) ctl_parser = CtlParser() ctl_parser.parse_ctl(options.ctlfile, options.start, options.end) else: ctl_parser = CtlParser({start: 'c', end: 'i'}) writer = SkoolWriter(snapshot, ctl_parser, options) writer.write_skool(options.write_refs, options.text)
def get_ctl_parser(ctls, infile, start, end, def_start, def_end): if infile[-4:].lower() in ('.bin', '.sna', '.szx', '.z80'): prefix = infile[:-4] else: prefix = infile if not ctls: ctls.extend(sorted(glob.glob(prefix + '*.ctl'))) ctlfiles = [] if ctls and '0' not in ctls: # Use control file(s) for ctl in ctls: if os.path.isdir(ctl): ctlfiles.extend(sorted(glob.glob(os.path.join(ctl, '*.ctl')))) else: ctlfiles.append(ctl) if ctlfiles: if len(ctlfiles) > 1: suffix = 's' else: suffix = '' info('Using control file{}: {}'.format(suffix, ', '.join(ctlfiles))) ctl_parser = CtlParser() ctl_parser.parse_ctls(ctlfiles, start, end) else: ctl_parser = CtlParser({def_start: 'c', def_end: 'i'}) return ctl_parser
def run(snafile, options, config): snapshot, start, end = make_snapshot(snafile, options.org, options.start, options.end, options.page) if options.sftfile: # Use a skool file template info('Using skool file template: {}'.format(options.sftfile)) writer = SftParser(snapshot, options.sftfile, config['DefbZfill'], options.base == 16, options.case == 1) writer.write_skool(options.start, options.end) return if options.ctlfiles: # Use control file(s) if len(options.ctlfiles) > 1: suffix = 's' else: suffix = '' info('Using control file{}: {}'.format(suffix, ', '.join(options.ctlfiles))) ctl_parser = CtlParser() ctl_parser.parse_ctls(options.ctlfiles, options.start, options.end) else: ctl_parser = CtlParser({start: 'c', end: 'i'}) writer = SkoolWriter(snapshot, ctl_parser, options, config) writer.write_skool(config['ListRefs'], config['Text'])
def test_s_directives_hex(self): snapshot = [] ctl = '\n'.join(( 's 00000', ' 00000,14,d2:b1,h2:128,h10:2', 'i 00014' )) ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) disassembly = Disassembly(snapshot, ctl_parser, True, asm_hex=True) entries = disassembly.entries self.assertEqual(len(entries), 2) entry = entries[0] self.assertEqual(entry.address, 0) instructions = entry.instructions actual_instructions = [(i.address, i.operation) for i in instructions] exp_instructions = [ (0, 'DEFS 2,%00000001'), (2, 'DEFS 2,$80'), (4, 'DEFS $0A,2') ] self.assertEqual(exp_instructions, actual_instructions)
def run(snafile, options, config): # Read the snapshot file if snafile[-4:].lower() in ('.sna', '.szx', '.z80'): snapshot = get_snapshot(snafile, options.page) start = max(START, options.start) else: ram = read_bin_file(snafile, 65536) if options.org is None: org = 65536 - len(ram) else: org = options.org snapshot = [0] * org snapshot.extend(ram) start = max(org, options.start) end = min(options.end, len(snapshot)) snapshot += [0] * (65536 - len(snapshot)) if options.sftfile: # Use a skool file template info('Using skool file template: {}'.format(options.sftfile)) writer = SftParser(snapshot, options.sftfile, options.zfill, options.base == 16, options.case == 1) writer.write_skool(options.start, options.end) return if options.genctlfile: # Generate a control file ctls = generate_ctls(snapshot, start, end, options.code_map) write_ctl(options.genctlfile, ctls, options.ctl_hex) ctl_parser = CtlParser(ctls) elif options.ctlfile: # Use a control file info('Using control file: {}'.format(options.ctlfile)) ctl_parser = CtlParser() ctl_parser.parse_ctl(options.ctlfile, options.start, options.end) else: ctl_parser = CtlParser({start: 'c', end: 'i'}) writer = SkoolWriter(snapshot, ctl_parser, options, config) writer.write_skool(options.write_refs, options.text)
def run(snafile, options, config): snapshot, start, end = make_snapshot(snafile, options.org, options.start, options.end, options.page) if options.ctlfiles: # Use control file(s) if len(options.ctlfiles) > 1: suffix = 's' else: suffix = '' info('Using control file{}: {}'.format(suffix, ', '.join(options.ctlfiles))) ctl_parser = CtlParser() ctl_parser.parse_ctls(options.ctlfiles, options.start, options.end) else: ctl_parser = CtlParser({start: 'c', end: 'i'}) writer = SkoolWriter(snapshot, ctl_parser, options, config) writer.write_skool(config['ListRefs'], config['Text'])
def _get_writer(self, snapshot, ctl, defb_size=8, defb_mod=1, zfill=False, defm_width=66, asm_hex=False, asm_lower=False): ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(ctl)) return SkoolWriter(snapshot, ctl_parser, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower)
def test_parse_ctl(self): ctl_parser = CtlParser() ctlfile = self.write_text_file(CTL) ctl_parser.parse_ctl(ctlfile) exp_ctls = { 30000: 'b', 30100: 'c', 30200: 'g', 30300: 'i', 30400: 't', 30500: 'u', 30600: 'w', 30700: 's' } self.assertEqual(exp_ctls, ctl_parser.ctls) exp_subctls = { 30002: 't', 30012: 'w', 30020: 'c', 30027: None, 30050: 'b', 30055: None, 30100: None, 30200: 'b', 30210: None, 30450: 't', 30457: None, 30500: 'b', 30502: 'b', 30505: None, 30510: 'b', 30522: None, 30530: 'b', 30550: None, 30560: 'b', 30581: None, 30620: 's', 30627: None, 30720: 'b', 30730: 't', 30745: None } self.assertEqual(exp_subctls, ctl_parser.subctls) exp_titles = { 30000: 'Data at 30000', 30100: 'Routine at 30100', 30200: 'Game status buffer entry at 30200', 30300: 'Ignored block at 30300', 30400: 'Message at 30400', 30500: 'Unused block at 30500', 30600: 'Words at 30600', 30700: 'Zeroes at 30700' } self.assertEqual(exp_titles, ctl_parser.titles) exp_instruction_comments = { 30002: 'Message in the data block', 30012: None, 30020: None, 30050: 'Complex DEFB with a blank directive', 30200: "Blank directive in a 'g' block", 30450: 'Complex DEFM with a blank directive', 30500: "Blank directive in a 'u' block", 30502: None, 30510: None, 30530: None, 30560: None, 30620: None, 30720: None, 30730: None } self.assertEqual(exp_instruction_comments, ctl_parser.instruction_comments) exp_comments = { 30100: ['Description of routine at 30100'] } self.assertEqual(exp_comments, ctl_parser.comments) exp_registers = { 30100: [['A', 'Some value'], ['BC', 'Some other value']] } self.assertEqual(exp_registers, ctl_parser.registers) exp_end_comments = { 30100: ['First paragraph of the end comment for the routine at 30100', 'Second paragraph of the end comment for the routine at 30100'] } self.assertEqual(exp_end_comments, ctl_parser.end_comments) exp_lengths = { 30050: [(5, [(3, None), (2, 'T')])], 30200: [(1, None)], 30450: [(7, [(4, None), (3, 'B')])], 30510: [(3, None)], 30530: list(zip([2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 3], [None] * 11)), 30560: list(zip([6, 5, 4, 3, 2, 1], [None] * 6)), 30720: [ (1, None), (5, [(3, 'T'), (2, None)]), (2, [(1, None), (1, 'T')]), (2, [(1, None), (1, 'T')]) ], 30730: [(15, [(10, None), (5, 'B')])] } self.assertEqual(exp_lengths, ctl_parser.lengths) exp_multiline_comments = { 30012: (30026, 'This comment covers the following two sub-blocks') } self.assertEqual(exp_multiline_comments, ctl_parser.multiline_comments) exp_entry_asm_directives = { 30000: [('start', None)] } self.assertEqual(exp_entry_asm_directives, ctl_parser.entry_asm_directives) exp_instruction_asm_directives = { 30101: [('label', 'LOOP')] } self.assertEqual(exp_instruction_asm_directives, ctl_parser.instruction_asm_directives)
def _generate_ctls_without_code_map(snapshot, start, end): ctls = {start: 'c', end: 'i'} # Look for potential 'RET', 'JR d' and 'JP nn' instructions and assume that # they end a block (after which another block follows); note that we don't # bother examining the final byte because no block can follow it for address in range(start, end - 1): b = snapshot[address] if b == 201: ctls[address + 1] = 'c' elif b == 195 and address < end - 3: ctls[address + 3] = 'c' elif b == 24 and address < end - 2: ctls[address + 2] = 'c' ctl_parser = CtlParser(ctls) disassembly = Disassembly(snapshot, ctl_parser) # Scan the disassembly for pairs of adjacent blocks that overlap, and join # such pairs while True: done = True for entry in disassembly.entries[:-1]: if entry.bad_blocks: del ctls[entry.next.address] disassembly.remove_entry(entry.address) disassembly.remove_entry(entry.next.address) done = False if done: break disassembly.build() # Scan the disassembly for blocks that don't end in a 'RET', 'JP nn' or # 'JR d' instruction, and join them to the next block changed = False for entry in disassembly.entries[:-1]: last_instr = entry.instructions[-1].operation if last_instr != 'RET' and not (last_instr[:2] in ('JP', 'JR') and last_instr[3:].isdigit()): next_address = entry.next.address if next_address < end: del ctls[entry.next.address] disassembly.remove_entry(entry.address) disassembly.remove_entry(entry.next.address) changed = True if changed: disassembly.build() # Scan the disassembly for pairs of adjacent blocks where the start address # of the second block is JRed or JPed to from the first block, and join # such pairs while True: done = True for entry in disassembly.entries[:-1]: for instruction in entry.instructions: operation = instruction.operation if operation[:2] in ('JR', 'JP') and operation[-5:] == str( entry.next.address): del ctls[entry.next.address] disassembly.remove_entry(entry.address) disassembly.remove_entry(entry.next.address) done = False break if done: break disassembly.build() # Mark any NOP sequences at the beginning of a block as a separate zero # block for entry in disassembly.entries: if entry.instructions[0].operation != 'NOP': continue for instruction in entry.instructions[1:]: if instruction.operation != 'NOP': break ctls[entry.address] = 's' if entry.instructions[-1].operation != 'NOP': ctls[instruction.address] = 'c' # See which blocks marked as code look like text or data _analyse_blocks(disassembly, ctls) return ctls
def test_disassembly(self): ctl_parser = CtlParser() ctl_parser.parse_ctl(self.write_text_file(DISASSEMBLY_CTL)) disassembly = Disassembly(DISASSEMBLY_SNAPSHOT, ctl_parser, True) entries = disassembly.entries self.assertEqual(len(entries), 16) # Entry #1 (32768) entry = entries[0] self.assertEqual(entry.address, 32768) self.assertEqual(entry.title, 'Routine at 32768') self.assertEqual(entry.description, ['Routine description paragraph 1.', 'Routine description paragraph 2.']) self.assertEqual(entry.ctl, 'c') self.assertEqual(entry.registers, [['A', 'Some value'], ['B', 'Some other value']]) self.assertEqual(entry.end_comment, ['Routine end comment.']) self.assertEqual(entry.start, True) self.assertEqual(entry.end, True) self.assertEqual(entry.writer, 'skoolkit.game.GameAsmWriter') self.assertEqual(entry.org, '32768') blocks = entry.blocks self.assertEqual(len(blocks), 1) block = blocks[0] self.assertEqual(block.header, None) self.assertEqual(block.comment, 'This is an instruction-level comment that spans two instructions') self.assertEqual(block.instructions, entry.instructions) self.assertEqual(block.end, 32770) instructions = entry.instructions self.assertEqual(len(instructions), 2) instruction = instructions[0] self.assertEqual(instruction.address, 32768) self.assertEqual(instruction.operation, 'XOR A') self.assertEqual(instruction.bytes, [175]) self.assertEqual(instruction.referrers, [entries[13]]) self.assertEqual(instruction.entry, entry) self.assertEqual(instruction.ctl, 'c') self.assertEqual(instruction.comment, None) self.assertEqual(instruction.asm_directives, [('label', 'START')]) # Entry #2 (32770) entry = entries[1] self.assertEqual(entry.address, 32770) self.assertEqual(entry.title, 'Message at {0}'.format(entry.address)) instructions = entry.instructions self.assertEqual(len(instructions), 2) self.assertEqual(instructions[0].operation, 'DEFM "Hi"') self.assertEqual(instructions[1].operation, 'DEFM "Lo"') # Entry #3 (32774) entry = entries[2] self.assertEqual(entry.address, 32774) self.assertEqual(entry.title, 'Yo') # Entry #4 (32776) entry = entries[3] self.assertEqual(entry.address, 32776) self.assertEqual(entry.title, 'Data block at {0}'.format(entry.address)) instructions = entry.instructions self.assertEqual(len(instructions), 4) self.assertEqual(instructions[0].operation, 'DEFB 0,0,0') self.assertEqual(instructions[3].operation, 'DEFB 0,0,0') # Entry #5 (32788) entry = entries[4] self.assertEqual(entry.address, 32788) self.assertEqual(entry.title, 'Important byte') # Entry #6 (32789) entry = entries[5] self.assertEqual(entry.address, 32789) self.assertEqual(entry.title, 'Data block at {0}'.format(entry.address)) instructions = entry.instructions self.assertEqual(len(instructions), 1) self.assertEqual(instructions[0].operation, 'DEFW 0,0') # Entry #7 (32793) entry = entries[6] self.assertEqual(entry.address, 32793) self.assertEqual(entry.title, 'Important word') # Entry #8 (32795) entry = entries[7] self.assertEqual(entry.address, 32795) self.assertEqual(entry.title, 'Game status buffer entry at {0}'.format(entry.address)) # Entry #9 (32796) entry = entries[8] self.assertEqual(entry.address, 32796) self.assertEqual(entry.title, 'Important game status buffer byte') # Entry #10 (32797) entry = entries[9] self.assertEqual(entry.address, 32797) self.assertEqual(entry.title, 'Unused') # Entry #11 (32798) entry = entries[10] self.assertEqual(entry.address, 32798) self.assertEqual(entry.title, 'Unimportant unused byte') # Entry #12 (32799) entry = entries[11] self.assertEqual(entry.address, 32799) self.assertEqual(entry.title, 'Unused') instructions = entry.instructions self.assertEqual(len(instructions), 2) self.assertEqual(instructions[0].operation, 'DEFS 5') self.assertEqual(instructions[1].operation, 'DEFS 5') # Entry #13 (32809) entry = entries[12] self.assertEqual(entry.address, 32809) self.assertEqual(entry.title, 'Block of zeroes') instructions = entry.instructions self.assertEqual(len(instructions), 3) self.assertEqual(instructions[0].operation, 'DEFS 3') self.assertEqual(instructions[1].operation, 'NOP') self.assertEqual(instructions[2].operation, 'NOP') blocks = entry.blocks self.assertEqual(len(blocks), 1) block = blocks[0] self.assertEqual(block.comment, 'A DEFS followed by two NOPs') # Entry #14 (32814) entry = entries[13] self.assertEqual(entry.address, 32814) self.assertEqual(entry.title, 'Refers to the routine at 32768') # Entry #15 (32817) entry = entries[14] self.assertEqual(entry.address, 32817) instructions = entry.instructions self.assertEqual(len(instructions), 6) self.assertEqual(instructions[0].operation, 'DEFM "Hi",1,2,3') self.assertEqual(instructions[1].operation, 'DEFB 4,5,"Lo",6') self.assertEqual(instructions[2].operation, 'DEFM "ab",1') self.assertEqual(instructions[3].operation, 'DEFM "cd",2') self.assertEqual(instructions[4].operation, 'DEFM "e",3') self.assertEqual(instructions[5].operation, 'DEFM "f",4') # Entry #16 (32837) entry = entries[15] self.assertEqual(entry.address, 32837)
def _generate_ctls_with_code_map(snapshot, start, end, config, code_map): # (1) Use the code map to create an initial set of 'c' ctls, and mark all # unexecuted blocks as 'U' (unknown) # (2) Where a 'c' block doesn't end with a RET/JP/JR, extend it up to the # next RET/JP/JR in the following 'U' blocks, or up to the next 'c' # block # (3) Mark entry points in 'U' blocks that are CALLed or JPed to from 'c' # blocks with 'c' # (4) Split 'c' blocks on RET/JP/JR # (5) Scan the disassembly for pairs of adjacent blocks where the start # address of the second block is JRed or JPed to from the first block, # and join such pairs # (6) Examine the remaining 'U' blocks for text # (7) Mark data blocks of all zeroes with 's' # (1) Mark all executed blocks as 'c' and unexecuted blocks as 'U' # (unknown) ctls = {start: 'U', end: 'i'} for address, length in _get_code_blocks(snapshot, start, end, code_map): ctls[address] = 'c' if address + length < end: ctls[address + length] = 'U' # (2) Where a 'c' block doesn't end with a RET/JP/JR, extend it up to the # next RET/JP/JR in the following 'U' blocks, or up to the next 'c' block while 1: done = True for ctl, b_start, b_end in _get_blocks(ctls): if ctl == 'c': last_op_id = list(decode(snapshot, b_start, b_end))[-1][3] if last_op_id == END: continue if _find_terminal_instruction(snapshot, ctls, b_end, end) < end: done = False break if done: break # (3) Mark entry points in 'U' blocks that are CALLed or JPed to from 'c' # blocks with 'c' ctl_parser = CtlParser(ctls) disassembly = Disassembly(snapshot, ctl_parser) while 1: disassembly.build(True) done = True for entry in disassembly.entries: if entry.ctl == 'U': for instruction in entry.instructions: for referrer in instruction.referrers: if ctls[referrer] == 'c': ctls[instruction.address] = 'c' if entry.next: e_end = entry.next.address else: e_end = 65536 _find_terminal_instruction(snapshot, ctls, instruction.address, e_end, entry.ctl) disassembly.remove_entry(entry.address) done = False break if not done: break if not done: break if done: break # (4) Split 'c' blocks on RET/JP/JR for ctl, b_address, b_end in _get_blocks(ctls): if ctl == 'c': next_address = _find_terminal_instruction(snapshot, ctls, b_address, b_end, 'c') if next_address < b_end: disassembly.remove_entry(b_address) while next_address < b_end: next_address = _find_terminal_instruction( snapshot, ctls, next_address, b_end, 'c') # (5) Scan the disassembly for pairs of adjacent blocks where the start # address of the second block is JRed or JPed to from the first block, and # join such pairs while 1: disassembly.build() done = True for entry in disassembly.entries[:-1]: if entry.ctl == 'c': for instruction in entry.instructions: operation = instruction.operation if operation[:2] in ('JR', 'JP') and operation[-5:] == str( entry.next.address): del ctls[entry.next.address] disassembly.remove_entry(entry.address) disassembly.remove_entry(entry.next.address) done = False break if done: break # (6) Examine the 'U' blocks for text/data for ctl, b_start, b_end in _get_blocks(ctls): if ctl == 'U': ctls[b_start] = 'b' for t_start, t_end in _get_text_blocks(snapshot, b_start, b_end, config): ctls[t_start] = 't' if t_end < b_end: ctls[t_end] = 'b' # (7) Mark data blocks of all zeroes with 's' for ctl, b_start, b_end in _get_blocks(ctls): if ctl == 'b' and sum(snapshot[b_start:b_end]) == 0: ctls[b_start] = 's' return ctls
def _get_ctl_parser(self, ctl, min_address=0, max_address=65536): ctl_parser = CtlParser() ctlfile = self.write_text_file(ctl) ctl_parser.parse_ctl(ctlfile, min_address, max_address) return ctl_parser