1
0
mirror of https://github.com/mnaberez/py65.git synced 2025-04-05 13:37:09 +00:00

Added disassembler.

This commit is contained in:
Mike Naberezny 2008-11-18 06:57:17 +00:00
parent fda4631664
commit 469a822a37
4 changed files with 494 additions and 0 deletions

169
src/py65/disassembler.py Normal file
View File

@ -0,0 +1,169 @@
class Disassembler:
_instructions = [
['BRK','imp'], ['ORA','inx'], ['???','imp'], ['???','imp'],
['???','imp'], ['ORA','zpg'], ['ASL','zpg'], ['???','imp'],
['PHP','imp'], ['ORA','imm'], ['ASL','acc'], ['???','imp'],
['???','imp'], ['ORA','abs'], ['ASL','abs'], ['???','imp'],
['BPL','rel'], ['ORA','iny'], ['???','imp'], ['???','imp'],
['???','imp'], ['ORA','zpx'], ['ASL','zpx'], ['???','imp'],
['CLC','imp'], ['ORA','aby'], ['???','imp'], ['???','imp'],
['???','imp'], ['ORA','abx'], ['ASL','abx'], ['???','imp'],
['JSR','abs'], ['AND','inx'], ['???','imp'], ['???','imp'],
['BIT','zpg'], ['AND','zpg'], ['ROL','zpg'], ['???','imp'],
['PLP','imp'], ['AND','imm'], ['ROL','acc'], ['???','imp'],
['BIT','abs'], ['AND','abs'], ['ROL','abs'], ['???','imp'],
['BMI','rel'], ['AND','iny'], ['???','imp'], ['???','imp'],
['???','imp'], ['AND','zpx'], ['ROL','zpx'], ['???','imp'],
['SEC','imp'], ['AND','aby'], ['???','imp'], ['???','imp'],
['???','imp'], ['AND','abx'], ['ROL','abx'], ['???','imp'],
['RTI','imp'], ['EOR','inx'], ['???','imp'], ['???','imp'],
['???','imp'], ['EOR','zpg'], ['LSR','zpg'], ['???','imp'],
['PHA','imp'], ['EOR','imm'], ['LSR','acc'], ['???','imp'],
['JMP','abs'], ['EOR','abs'], ['LSR','abs'], ['???','imp'],
['BVC','rel'], ['EOR','iny'], ['???','imp'], ['???','imp'],
['???','imp'], ['EOR','zpx'], ['LSR','zpx'], ['???','imp'],
['CLI','imp'], ['EOR','aby'], ['???','imp'], ['???','imp'],
['???','imp'], ['EOR','abx'], ['LSR','abx'], ['???','imp'],
['RTS','imp'], ['ADC','inx'], ['???','imp'], ['???','imp'],
['???','imp'], ['ADC','zpg'], ['ROR','zpg'], ['???','imp'],
['PLA','imp'], ['ADC','imm'], ['ROR','acc'], ['???','imp'],
['JMP','ind'], ['ADC','abs'], ['ROR','abs'], ['???','imp'],
['BVS','rel'], ['ADC','iny'], ['???','imp'], ['???','imp'],
['???','imp'], ['ADC','zpx'], ['ROR','zpx'], ['???','imp'],
['SEI','imp'], ['ADC','aby'], ['???','imp'], ['???','imp'],
['???','imp'], ['ADC','abx'], ['ROR','abx'], ['???','imp'],
['???','imp'], ['STA','inx'], ['???','imp'], ['???','imp'],
['STY','zpg'], ['STA','zpg'], ['STX','zpg'], ['???','imp'],
['DEY','imp'], ['???','imp'], ['TXA','imp'], ['???','imp'],
['STY','abs'], ['STA','abs'], ['STX','abs'], ['???','imp'],
['BCC','rel'], ['STA','iny'], ['???','imp'], ['???','imp'],
['STY','zpx'], ['STA','zpx'], ['STX','zpy'], ['???','imp'],
['TYA','imp'], ['STA','aby'], ['TXS','imp'], ['???','imp'],
['???','imp'], ['STA','abx'], ['???','imp'], ['???','imp'],
['LDY','imm'], ['LDA','inx'], ['LDX','imm'], ['???','imp'],
['LDY','zpg'], ['LDA','zpg'], ['LDX','zpg'], ['???','imp'],
['TAY','imp'], ['LDA','imm'], ['TAX','imp'], ['???','imp'],
['LDY','abs'], ['LDA','abs'], ['LDX','abs'], ['???','imp'],
['BCS','rel'], ['LDA','iny'], ['???','imp'], ['???','imp'],
['LDY','zpx'], ['LDA','zpx'], ['LDX','zpy'], ['???','imp'],
['CLV','imp'], ['LDA','aby'], ['TSX','imp'], ['???','imp'],
['LDY','abx'], ['LDA','abx'], ['LDX','aby'], ['???','imp'],
['CPY','imm'], ['CMP','inx'], ['???','imp'], ['???','imp'],
['CPY','zpg'], ['CMP','zpg'], ['DEC','zpg'], ['???','imp'],
['INY','imp'], ['CMP','imm'], ['DEX','imp'], ['???','imp'],
['CPY','abs'], ['CMP','abs'], ['DEC','abs'], ['???','imp'],
['BNE','rel'], ['CMP','iny'], ['???','imp'], ['???','imp'],
['???','imp'], ['CMP','zpx'], ['DEC','zpx'], ['???','imp'],
['CLD','imp'], ['CMP','aby'], ['???','imp'], ['???','imp'],
['???','imp'], ['CMP','abx'], ['DEC','abx'], ['???','imp'],
['CPX','imm'], ['SBC','inx'], ['???','imp'], ['???','imp'],
['CPX','zpg'], ['SBC','zpg'], ['INC','zpg'], ['???','imp'],
['INX','imp'], ['SBC','imm'], ['NOP','imp'], ['???','imp'],
['CPX','abs'], ['SBC','abs'], ['INC','abs'], ['???','imp'],
['BEQ','rel'], ['SBC','iny'], ['???','imp'], ['???','imp'],
['???','imp'], ['SBC','zpx'], ['INC','zpx'], ['???','imp'],
['SED','imp'], ['SBC','aby'], ['???','imp'], ['???','imp'],
['???','imp'], ['SBC','abx'], ['INC','abx'], ['???','imp']
]
def __init__(self, mpu, address_parser):
self._mpu = mpu
self._address_parser = address_parser
def instruction_at(self, pc):
""" Disassemble the instruction at PC and return a tuple
containing (instruction byte count, human readable text)
"""
instruction = self._mpu.ByteAt(pc)
disasm, addressing = self._instructions[instruction]
if addressing == 'acc':
disasm += ' A'
elif addressing == 'abs':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
disasm += ' ' + address_or_label
length = 3
elif addressing == 'abx':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
disasm += ' %s,X' % address_or_label
length = 3
elif addressing == 'aby':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
disasm += ' %s,Y' % address_or_label
length = 3
elif addressing == 'imm':
byte = self._mpu.ByteAt(pc + 1)
disasm += ' #$%02x' % byte
length = 2
elif addressing == 'imp':
length = 1
elif addressing == 'ind':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
disasm += ' (%s)' % address_or_label
length = 3
elif addressing == 'iny':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
disasm += ' (%s),Y' % address_or_label
length = 2
elif addressing == 'inx':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
disasm += ' (%s,X)' % address_or_label
length = 2
elif addressing == 'rel':
opv = self._mpu.ByteAt(pc + 1)
targ = pc + 2
if opv & 128:
targ -= (opv ^ 255) + 1
else:
targ += opv
targ &= 0xffff
address_or_label = self._address_parser.label_for(targ,
'$%04x' % targ)
disasm += ' ' + address_or_label
length = 2
elif addressing == 'zpg':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
disasm += ' %s' % address_or_label
length = 2
elif addressing == 'zpx':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
disasm += ' %s,X' % address_or_label
length = 2
elif addressing == 'zpy':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
disasm += ' %s,Y' % address_or_label
length = 2
return (length, disasm)

View File

@ -7,6 +7,7 @@ import shlex
import asyncore
import sys
from py65.mpu6502 import MPU
from py65.disassembler import Disassembler
from py65.util import itoa, AddressParser, getch
from py65.memory import ObservableMemory
@ -18,6 +19,7 @@ class Monitor(cmd.Cmd):
self._install_mpu_observers()
self._update_prompt()
self._address_parser = AddressParser()
self._disassembler = Disassembler(self._mpu, self._address_parser)
cmd.Cmd.__init__(self, completekey, stdin, stdout)
def onecmd(self, line):
@ -105,11 +107,34 @@ class Monitor(cmd.Cmd):
def help_quit(self):
return self.help_EOF()
def do_disassemble(self, args):
start, end = self._address_parser.range(args)
if start == end:
end += 1
address = start
while address < end:
bytes, disasm = self._disassembler.instruction_at(address)
mem = ''
for byte in self._mpu.memory[address:address+bytes]:
mem += '%02x ' % byte
line = "$%04x %-10s%s" % (address, mem, disasm)
self._output(line)
address += bytes
def help_disassemble(self):
self._output("disassemble <address_range>")
self._output("Disassemble instructions in the address range.")
def help_step(self):
self._output("Single-step through instructions.")
def do_step(self, args):
self._mpu.step()
self.do_disassemble('%04x' % self._mpu.pc)
def help_return(self):
self._output("Continues execution and returns to the monitor just")
@ -119,6 +144,10 @@ class Monitor(cmd.Cmd):
returns = [0x60, 0x40] # RTS, RTI
self._run(stopcodes=returns)
def help_goto(self):
self._output("goto <address>")
self._output("Change the PC to address and continue execution.")
def do_goto(self, args):
self._mpu.pc = self._address_parser.number(args)
brks = [0x00] # BRK
@ -295,6 +324,10 @@ class Monitor(cmd.Cmd):
out += "%02x " % byte
self._output(out)
def help_add_label(self):
self._output("add_label <address> <label>")
self._output("Map a given address to a label.")
def do_add_label(self, args):
split = shlex.split(args)
if len(split) != 2:
@ -306,6 +339,10 @@ class Monitor(cmd.Cmd):
self._address_parser.labels[label] = address
def help_show_labels(self):
self._output("show_labels")
self._output("Display current label mappings.")
def do_show_labels(self, args):
values = self._address_parser.labels.values()
keys = self._address_parser.labels.keys()
@ -315,6 +352,10 @@ class Monitor(cmd.Cmd):
for address, label in byaddress:
self._output("%04x: %s" % (address, label))
def help_delete_label(self):
self._output("delete_label <label>")
self._output("Remove the specified label from the label tables.")
def do_delete_label(self, args):
try:
del self._address_parser.labels[args]

View File

@ -0,0 +1,276 @@
import unittest
import sys
from py65.mpu6502 import MPU
from py65.disassembler import Disassembler
from py65.util import AddressParser
class DisassemblerTests(unittest.TestCase):
def test_disassembles_00(self):
length, disasm = self.disassemble([0x00])
self.assertEqual(1, length)
self.assertEqual('BRK', disasm)
def test_disassembles_01(self):
length, disasm = self.disassemble([0x01, 0x44])
self.assertEqual(2, length)
self.assertEqual('ORA ($44,X)', disasm)
def test_disassembles_02(self):
length, disasm = self.disassemble([0x02])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_03(self):
length, disasm = self.disassemble([0x03])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_04(self):
length, disasm = self.disassemble([0x04])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_05(self):
length, disasm = self.disassemble([0x05, 0x44])
self.assertEqual(2, length)
self.assertEqual('ORA $44', disasm)
def test_disassembles_06(self):
length, disasm = self.disassemble([0x06, 0x44])
self.assertEqual(2, length)
self.assertEqual('ASL $44', disasm)
def test_disassembles_07(self):
length, disasm = self.disassemble([0x07])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_08(self):
length, disasm = self.disassemble([0x08])
self.assertEqual(1, length)
self.assertEqual('PHP', disasm)
def test_disassembles_09(self):
length, disasm = self.disassemble([0x09, 0x44])
self.assertEqual(2, length)
self.assertEqual('ORA #$44', disasm)
def test_disassembles_10(self):
length, disasm = self.disassemble([0x10, 0x44])
self.assertEqual(2, length)
self.assertEqual('BPL $0046', disasm)
def test_disassembles_11(self):
length, disasm = self.disassemble([0x11, 0x44])
self.assertEqual(2, length)
self.assertEqual('ORA ($44),Y', disasm)
def test_disassembles_12(self):
length, disasm = self.disassemble([0x12])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_13(self):
length, disasm = self.disassemble([0x13])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_14(self):
length, disasm = self.disassemble([0x14])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_15(self):
length, disasm = self.disassemble([0x15, 0x44])
self.assertEqual(2, length)
self.assertEqual('ORA $44,X', disasm)
def test_disassembles_16(self):
length, disasm = self.disassemble([0x16, 0x44])
self.assertEqual(2, length)
self.assertEqual('ASL $44,X', disasm)
def test_disassembles_17(self):
length, disasm = self.disassemble([0x17])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_18(self):
length, disasm = self.disassemble([0x18])
self.assertEqual(1, length)
self.assertEqual('CLC', disasm)
def test_disassembles_19(self):
length, disasm = self.disassemble([0x19, 0x00, 0x44])
self.assertEqual(3, length)
self.assertEqual('ORA $4400,Y', disasm)
def test_disassembles_20(self):
length, disasm = self.disassemble([0x20, 0x97, 0x55])
self.assertEqual(3, length)
self.assertEqual('JSR $5597', disasm)
def test_disassembles_21(self):
length, disasm = self.disassemble([0x21, 0x44])
self.assertEqual(2, length)
self.assertEqual('AND ($44,X)', disasm)
def test_disassembles_22(self):
length, disasm = self.disassemble([0x22])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_23(self):
length, disasm = self.disassemble([0x23])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_24(self):
length, disasm = self.disassemble([0x24, 0x44])
self.assertEqual(2, length)
self.assertEqual('BIT $44', disasm)
def test_disassembles_25(self):
length, disasm = self.disassemble([0x25, 0x44])
self.assertEqual(2, length)
self.assertEqual('AND $44', disasm)
def test_disassembles_26(self):
length, disasm = self.disassemble([0x26, 0x44])
self.assertEqual(2, length)
self.assertEqual('ROL $44', disasm)
def test_disassembles_27(self):
length, disasm = self.disassemble([0x27])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_28(self):
length, disasm = self.disassemble([0x28])
self.assertEqual(1, length)
self.assertEqual('PLP', disasm)
def test_disassembles_29(self):
length, disasm = self.disassemble([0x29, 0x44])
self.assertEqual(2, length)
self.assertEqual('AND #$44', disasm)
def test_disassembles_30(self):
length, disasm = self.disassemble([0x30, 0x44])
self.assertEqual(2, length)
self.assertEqual('BMI $0046', disasm)
def test_disassembles_31(self):
length, disasm = self.disassemble([0x31, 0x44])
self.assertEqual(2, length)
self.assertEqual('AND ($44),Y', disasm)
def test_disassembles_32(self):
length, disasm = self.disassemble([0x32])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_33(self):
length, disasm = self.disassemble([0x33])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_34(self):
length, disasm = self.disassemble([0x34])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_35(self):
length, disasm = self.disassemble([0x35, 0x44])
self.assertEqual(2, length)
self.assertEqual('AND $44,X', disasm)
def test_disassembles_36(self):
length, disasm = self.disassemble([0x36, 0x44])
self.assertEqual(2, length)
self.assertEqual('ROL $44,X', disasm)
def test_disassembles_37(self):
length, disasm = self.disassemble([0x37])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_38(self):
length, disasm = self.disassemble([0x38])
self.assertEqual(1, length)
self.assertEqual('SEC', disasm)
def test_disassembles_39(self):
length, disasm = self.disassemble([0x39, 0x00, 0x44])
self.assertEqual(3, length)
self.assertEqual('AND $4400,Y', disasm)
def test_disassembles_40(self):
length, disasm = self.disassemble([0x40])
self.assertEqual(1, length)
self.assertEqual('RTI', disasm)
def test_disassembles_41(self):
length, disasm = self.disassemble([0x41, 0x44])
self.assertEqual(2, length)
self.assertEqual('EOR ($44,X)', disasm)
def test_disassembles_42(self):
length, disasm = self.disassemble([0x42])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_43(self):
length, disasm = self.disassemble([0x43])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_44(self):
length, disasm = self.disassemble([0x44])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_45(self):
length, disasm = self.disassemble([0x45, 0x44])
self.assertEqual(2, length)
self.assertEqual('EOR $44', disasm)
def test_disassembles_46(self):
length, disasm = self.disassemble([0x46, 0x44])
self.assertEqual(2, length)
self.assertEqual('LSR $44', disasm)
def test_disassembles_47(self):
length, disasm = self.disassemble([0x47])
self.assertEqual(1, length)
self.assertEqual('???', disasm)
def test_disassembles_48(self):
length, disasm = self.disassemble([0x48])
self.assertEqual(1, length)
self.assertEqual('PHA', disasm)
def test_disassembles_49(self):
length, disasm = self.disassemble([0x49, 0x44])
self.assertEqual(2, length)
self.assertEqual('EOR #$44', disasm)
def test_disassembles_50(self):
length, disasm = self.disassemble([0x50, 0x44])
self.assertEqual(2, length)
self.assertEqual('BVC $0046', disasm)
# Test Helpers
def disassemble(self, bytes):
mpu = MPU()
address_parser = AddressParser()
disasm = Disassembler(mpu, address_parser)
mpu.memory[0:len(bytes)-1] = bytes
return disasm.instruction_at(0)
def test_suite():
return unittest.findTestCases(sys.modules[__name__])
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')

View File

@ -16,6 +16,14 @@ class AddressParser:
self.radix = radix
self.labels = labels
def label_for(self, address, default=None):
"""Given an address, return the corresponding label or a default.
"""
for label, label_address in self.labels.iteritems():
if label_address == address:
return label
return default
def number(self, num):
"""Parse a string containing a label or number into an address.
"""