def disassemble(self, bytes, pc=0, mpu=None): if mpu is None: mpu = MPU() address_parser = AddressParser() disasm = Disassembler(mpu, address_parser) mpu.memory[pc:len(bytes) - 81] = bytes return disasm.instruction_at(pc)
def _reset(self, mpu_type,getc_addr=0xF004,putc_addr=0xF001): self._mpu = mpu_type() self.addrWidth = self._mpu.ADDR_WIDTH self.byteWidth = self._mpu.BYTE_WIDTH self.addrFmt = self._mpu.ADDR_FORMAT self.byteFmt = self._mpu.BYTE_FORMAT self.addrMask = self._mpu.addrMask self.byteMask = self._mpu.byteMask self._install_mpu_observers(getc_addr,putc_addr) self._address_parser = AddressParser() self._disassembler = Disassembler(self._mpu, self._address_parser) self._assembler = Assembler(self._mpu, self._address_parser)
def __init__(self): self.mem = Memory() self.cpu = MPU() self.patches = Patches() self.dis = Disassembler(self.cpu) pc_low = self.mem[0xfffc] pc_hi = self.mem[0xfffd] self.cpu.memory = self.mem self.cpu.pc = (pc_hi << 8) + pc_low self.trace = False
def _dont_test_disassemble_wraps_after_top_of_mem(self): ''' TODO: This test fails with IndexError. We should fix this so that it does not attempt to index memory out of range. It does not affect most Py65 users because py65mon uses ObservableMemory, which does not raise IndexError. ''' mpu = MPU() mpu.memory[0xFFFF] = 0x20 # JSR mpu.memory[0x0000] = 0xD2 # mpu.memory[0x0001] = 0xFF # $FFD2 dis = Disassembler(mpu) length, disasm = dis.instruction_at(0xFFFF) self.assertEqual(3, length) self.assertEqual('JSR $ffd2', disasm)
def __init__(self): self.mpu = NMOS6502() self.address_parser = AddressParser() # self.assembler = Assembler(self.mpu, self.address_parser) m = ObservableMemory(addrWidth=self.mpu.ADDR_WIDTH) self.mpu.memory = m self.disassembler = Disassembler(self.mpu, self.address_parser)
class C64(object): def __init__(self): self.mem = Memory() self.cpu = MPU() self.patches = Patches() self.dis = Disassembler(self.cpu) pc_low = self.mem[0xfffc] pc_hi = self.mem[0xfffd] self.cpu.memory = self.mem self.cpu.pc = (pc_hi << 8) + pc_low self.trace = False def step(self): pc = self.cpu.pc if self.trace: print "0x%04x - %s" % (pc, self.dis.instruction_at(pc)[1]) if kernal.base <= pc and pc <= kernal.end: kernal_function = kernal.resolve(pc) if kernal_function: kernal_function(self) self.cpu.inst_0x60() # RTS return patch = self.patches[pc] if patch: patch(self) return self.cpu.step() def run_for(self, cycles): for x in xrange(cycles): self.step() print print 'Emulation over, did %d CPU cycles.' % cycles def run_until(self, end_pc): while self.cpu.pc != end_pc: self.step()
class Monitor(cmd.Cmd): Microprocessors = {'6502': NMOS6502, '65C02': CMOS65C02, '65Org16': V65Org16} def __init__(self, mpu_type=NMOS6502, completekey='tab', stdin=None, stdout=None, argv=None): self.mpu_type = mpu_type self.putc_addr = 0xF001 self.getc_addr = 0xF004 if argv is None: argv = sys.argv self._breakpoints = [] self._width = 78 self.prompt = "." self._add_shortcuts() cmd.Cmd.__init__(self, completekey, stdin, stdout) self._parse_args(argv) self._reset(self.mpu_type,self.getc_addr,self.putc_addr) def _parse_args(self, argv): try: shortopts = 'hi:o:m:l:r:g:' longopts = ['help', 'mpu=', 'input=', 'output=', 'load=', 'rom=', 'goto='] options, args = getopt.getopt(argv[1:], shortopts, longopts) except getopt.GetoptError as exc: self._output(exc.args[0]) self._usage() self._exit(1) load = None rom = None goto = None mpu = None for opt, value in options: if opt in ('-i', '--input'): self.getc_addr = int(value, 16) if opt in ('-o', '--output'): self.putc_addr = int(value, 16) if opt in ('-l', '--load'): load = value if opt in ('-r', '--rom'): rom = value if opt in ('-g', '--goto'): goto = value if opt in ('-m', '--mpu'): mpu = value elif opt in ("-h", "--help"): self._usage() self._exit(0) if (mpu is not None) or (rom is not None): if mpu is None: mpu = "6502" if self._get_mpu(mpu) is None: mpus = list(self.Microprocessors.keys()) mpus.sort() msg = "Fatal: no such MPU. Available MPUs: %s" self._output(msg % ', '.join(mpus)) sys.exit(1) cmd = "mpu %s" % mpu self.onecmd(cmd) if load is not None: cmd = "load %s" % load self.onecmd(cmd) if goto is not None: cmd = "goto %s" % goto self.onecmd(cmd) if rom is not None: # load a ROM and run from the reset vector cmd = "load '%s' top" % rom self.onecmd(cmd) physMask = self._mpu.memory.physMask reset = self._mpu.RESET & physMask dest = self._mpu.memory[reset] + \ (self._mpu.memory[reset + 1] << self.byteWidth) cmd = "goto %08x" % dest self.onecmd(cmd) def _usage(self): usage = __doc__ % sys.argv[0] self._output(usage) def onecmd(self, line): line = self._preprocess_line(line) result = None try: result = cmd.Cmd.onecmd(self, line) except KeyboardInterrupt: self._output("Interrupt") except Exception: (file, fun, line), t, v, tbinfo = compact_traceback() error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) self._output(error) if not line.startswith("quit"): self._output_mpu_status() return result def _reset(self, mpu_type,getc_addr=0xF004,putc_addr=0xF001): self._mpu = mpu_type() self.addrWidth = self._mpu.ADDR_WIDTH self.byteWidth = self._mpu.BYTE_WIDTH self.addrFmt = self._mpu.ADDR_FORMAT self.byteFmt = self._mpu.BYTE_FORMAT self.addrMask = self._mpu.addrMask self.byteMask = self._mpu.byteMask self._install_mpu_observers(getc_addr,putc_addr) self._address_parser = AddressParser() self._disassembler = Disassembler(self._mpu, self._address_parser) self._assembler = Assembler(self._mpu, self._address_parser) def _add_shortcuts(self): self._shortcuts = {'EOF': 'quit', '~': 'tilde', 'a': 'assemble', 'ab': 'add_breakpoint', 'al': 'add_label', 'd': 'disassemble', 'db': 'delete_breakpoint', 'dl': 'delete_label', 'exit': 'quit', 'f': 'fill', '>': 'fill', 'g': 'goto', 'h': 'help', '?': 'help', 'l': 'load', 'm': 'mem', 'q': 'quit', 'r': 'registers', 'ret': 'return', 'rad': 'radix', 's': 'save', 'shb': 'show_breakpoints', 'shl': 'show_labels', 'x': 'quit', 'z': 'step'} def _preprocess_line(self, line): # line comments quoted = False for pos, char in enumerate(line): if char in ('"', "'"): quoted = not quoted if (not quoted) and (char == ';'): line = line[:pos] break # whitespace & leading dots line = line.strip(' \t').lstrip('.') # special case for vice compatibility if line.startswith('~'): line = self._shortcuts['~'] + ' ' + line[1:] # command shortcuts for shortcut, command in self._shortcuts.items(): if line == shortcut: line = command break pattern = '^%s\s+' % re.escape(shortcut) matches = re.match(pattern, line) if matches: start, end = matches.span() line = "%s %s" % (command, line[end:]) break return line def _get_mpu(self, name): requested = name.lower() mpu = None for key, klass in self.Microprocessors.items(): if key.lower() == requested: mpu = klass break return mpu def _install_mpu_observers(self,getc_addr,putc_addr): def putc(address, value): try: self.stdout.write(chr(value)) except UnicodeEncodeError: # Python 3 self.stdout.write("?") self.stdout.flush() def getc(address): char = console.getch_noblock(self.stdin) if char: byte = ord(char) else: byte = 0 return byte m = ObservableMemory(addrWidth=self.addrWidth) m.subscribe_to_write([self.putc_addr], putc) m.subscribe_to_read([self.getc_addr], getc) self._mpu.memory = m def _output_mpu_status(self): self._output("\n" + repr(self._mpu)) def _output(self, stuff): self.stdout.write("%s\n" % stuff) def _exit(self, exitcode=0): sys.exit(exitcode) def do_help(self, args): args = self._shortcuts.get(args.strip(), args) return cmd.Cmd.do_help(self, args) def help_version(self): self._output("version\t\tDisplay Py65 version information.") def do_version(self, args): self._output("\nPy65 Monitor") def help_help(self): self._output("help\t\tPrint a list of available actions.") self._output("help <action>\tPrint help for <action>.") def help_reset(self): self._output("reset\t\tReset the microprocessor") def do_reset(self, args): klass = self._mpu.__class__ self._reset(mpu_type=klass) def do_mpu(self, args): def available_mpus(): mpus = list(self.Microprocessors.keys()) mpus.sort() self._output("Available MPUs: %s" % ', '.join(mpus)) if args == '': self._output("Current MPU is %s" % self._mpu.name) available_mpus() else: new_mpu = self._get_mpu(args) if new_mpu is None: self._output("Unknown MPU: %s" % args) available_mpus() else: self._reset(new_mpu,self.getc_addr,self.putc_addr) self._output("Reset with new MPU %s" % self._mpu.name) def help_mpu(self): self._output("mpu\t\tPrint available microprocessors.") self._output("mpu <type>\tSelect a new microprocessor.") def do_quit(self, args): self._output('') return 1 def help_quit(self): self._output("To quit, type ^D or use the quit command.") def do_assemble(self, args): splitted = args.split(None, 1) if len(splitted) != 2: return self._interactive_assemble(args) statement = splitted[1] try: start = self._address_parser.number(splitted[0]) bytes = self._assembler.assemble(statement, start) end = start + len(bytes) self._mpu.memory[start:end] = bytes self.do_disassemble(self.addrFmt % start) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" except OverflowError: self._output("Overflow error: %s" % args) except SyntaxError: self._output("Syntax error: %s" % statement) def help_assemble(self): self._output("assemble\t\t\t" "Start interactive assembly at the program counter.") self._output("assemble <address>\t\t" "Start interactive assembly at the address.") self._output("assemble <address> <statement>\t" "Assemble a statement at the address.") def _interactive_assemble(self, args): if args == '': start = self._mpu.pc else: try: start = self._address_parser.number(args) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" return while True: prompt = "\r$" + (self.addrFmt % start) + " " + \ (" " * int(1 + self.byteWidth / 4) * 3) line = console.line_input(prompt, stdin=self.stdin, stdout=self.stdout) if not line.strip(): self.stdout.write("\n") return # assemble into memory try: bytes = self._assembler.assemble(line, pc=start) numbytes = len(bytes) end = start + numbytes self._mpu.memory[start:end] = bytes # print disassembly _, disasm = self._disassembler.instruction_at(start) fdisasm = self._format_disassembly(start, numbytes, disasm) indent = ' ' * (len(prompt + line) + 5) self.stdout.write("\r" + indent + "\r") self.stdout.write(fdisasm + "\n") # advance to next address start += numbytes if start >= (2 ** self._mpu.ADDR_WIDTH): start = 0 except KeyError: addr = self.addrFmt % start self.stdout.write("\r$%s ?Label\n" % addr) except OverflowError: addr = self.addrFmt % start self.stdout.write("\r$%s ?Overflow\n" % addr) except SyntaxError: addr = self.addrFmt % start self.stdout.write("\r$%s ?Syntax\n" % addr) def do_disassemble(self, args): splitted = shlex.split(args) if len(splitted) != 1: return self.help_disassemble() address_parts = splitted[0].split(":") start = self._address_parser.number(address_parts[0]) if len(address_parts) > 1: end = self._address_parser.number(address_parts[1]) else: end = start max_address = (2 ** self._mpu.ADDR_WIDTH) - 1 cur_address = start needs_wrap = start > end while needs_wrap or cur_address <= end: length, disasm = self._disassembler.instruction_at(cur_address) self._output(self._format_disassembly(cur_address, length, disasm)) remaining = length while remaining: remaining -= 1 cur_address += 1 if start > end and cur_address > max_address: needs_wrap = False cur_address = 0 def _format_disassembly(self, address, length, disasm): cur_address = address max_address = (2 ** self._mpu.ADDR_WIDTH) - 1 bytes_remaining = length dump = '' while bytes_remaining: if cur_address > max_address: cur_address = 0 dump += self.byteFmt % self._mpu.memory[cur_address] + " " cur_address += 1 bytes_remaining -= 1 fieldwidth = 1 + int(1 + self.byteWidth / 4) * 3 fieldfmt = "%%-%ds" % fieldwidth return "$" + self.addrFmt % address + " " + fieldfmt % dump + disasm def help_disassemble(self): self._output("disassemble <address_range>") self._output("Disassemble instructions in the address range.") self._output('Range is specified like "<start>:<end>".') def help_step(self): self._output("step") self._output("Single-step through instructions.") def do_step(self, args): self._mpu.step() self.do_disassemble(self.addrFmt % self._mpu.pc) def help_return(self): self._output("return") self._output("Continues execution and returns to the monitor just") self._output("before the next RTS or RTI is executed.") def do_return(self, args): 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): if args == '': return self.help_goto() self._mpu.pc = self._address_parser.number(args) brks = [0x00] # BRK self._run(stopcodes=brks) def _run(self, stopcodes): stopcodes = set(stopcodes) breakpoints = set(self._breakpoints) mpu = self._mpu mem = self._mpu.memory if not breakpoints: while True: mpu.step() if mem[mpu.pc] in stopcodes: break else: while True: mpu.step() pc = mpu.pc if mem[pc] in stopcodes: break if pc in breakpoints: msg = "Breakpoint %d reached." self._output(msg % self._breakpoints.index(pc)) break def help_radix(self): self._output("radix [H|D|O|B]") self._output("Set default radix to hex, decimal, octal, or binary.") self._output("With no argument, the current radix is printed.") def help_cycles(self): self._output("Display the total number of cycles executed.") def do_cycles(self, args): self._output(str(self._mpu.processorCycles)) def do_radix(self, args): radixes = {'Hexadecimal': 16, 'Decimal': 10, 'Octal': 8, 'Binary': 2} if args != '': new = args[0].lower() changed = False for name, radix in radixes.items(): if name[0].lower() == new: self._address_parser.radix = radix changed = True if not changed: self._output("Illegal radix: %s" % args) for name, radix in radixes.items(): if self._address_parser.radix == radix: self._output("Default radix is %s" % name) def help_tilde(self): self._output("~ <number>") self._output("Display a number in decimal, hex, octal, and binary.") def do_tilde(self, args): if args == '': return self.help_tilde() try: num = self._address_parser.number(args) self._output("+%u" % num) self._output("$" + self.byteFmt % num) self._output("%04o" % num) self._output(itoa(num, 2).zfill(8)) except KeyError: self._output("Bad label: %s" % args) except OverflowError: self._output("Overflow error: %s" % args) def help_registers(self): self._output("registers[<name>=<value> [, <name>=<value>]*]") self._output("Assign respective registers. With no parameters,") self._output("display register values.") def do_registers(self, args): if args == '': return pairs = re.findall('([^=,\s]*)=([^=,\s]*)', args) if pairs == []: return self._output("Syntax error: %s" % args) for register, value in pairs: if register not in ('pc', 'sp', 'a', 'x', 'y', 'p'): self._output("Invalid register: %s" % register) else: try: intval = self._address_parser.number(value) except KeyError as exc: # label not found self._output(exc.args[0]) continue except OverflowError as exc: # wider than address space msg = "Overflow: %r too wide for register %r" self._output(msg % (value, register)) continue if register != 'pc': if intval != (intval & self.byteMask): msg = "Overflow: %r too wide for register %r" self._output(msg % (value, register)) continue setattr(self._mpu, register, intval) def help_cd(self): self._output("cd <directory>") self._output("Change the working directory.") def do_cd(self, args): if args == '': return self.help_cd() try: os.chdir(args) except OSError as exc: msg = "Cannot change directory: [%d] %s" % (exc.errno, exc.strerror) self._output(msg) self.do_pwd() def help_pwd(self): self._output("Show the current working directory.") def do_pwd(self, args=None): cwd = os.getcwd() self._output(cwd) def help_load(self): self._output("load <filename|url> <address|top>") self._output("Load a file into memory at the specified address.") self._output('An address of "top" loads into the top of memory.') self._output("Commodore-style load address bytes are ignored.") def do_load(self, args): split = shlex.split(args) if len(split) not in (1, 2): self._output("Syntax error: %s" % args) return filename = split[0] if "://" in filename: try: f = urlopen(filename) bytes = f.read() f.close() except Exception as exc: msg = "Cannot fetch remote file: %s" % str(exc) self._output(msg) return else: try: f = open(filename, 'rb') bytes = f.read() f.close() except (OSError, IOError) as exc: msg = "Cannot load file: [%d] %s" % (exc.errno, exc.strerror) self._output(msg) return if len(split) == 2: if split[1] == "top": # load a ROM to top of memory top_address = self.addrMask program_size = len(bytes) // (self.byteWidth // 8) start = top_address - program_size + 1 else: start = self._address_parser.number(split[1]) else: start = self._mpu.pc if self.byteWidth == 8: if isinstance(bytes, str): bytes = map(ord, bytes) else: # Python 3 bytes = [ b for b in bytes ] elif self.byteWidth == 16: def format(msb, lsb): if isinstance(bytes, str): return (ord(msb) << 8) + ord(lsb) else: # Python 3 return (msb << 8) + lsb bytes = list(map(format, bytes[0::2], bytes[1::2])) self._fill(start, start, bytes) def help_save(self): self._output("save \"filename\" <start> <end>") self._output("Save the specified memory range as a binary file.") self._output("Commodore-style load address bytes are not written.") def do_save(self, args): split = shlex.split(args) if len(split) != 3: self._output("Syntax error: %s" % args) return filename = split[0] start = self._address_parser.number(split[1]) end = self._address_parser.number(split[2]) mem = self._mpu.memory[start:end + 1] try: f = open(filename, 'wb') for m in mem: # output each octect from msb first for shift in range(self.byteWidth - 8, -1, -8): f.write(bytearray([(m >> shift) & 0xff])) f.close() except (OSError, IOError) as exc: msg = "Cannot save file: [%d] %s" % (exc.errno, exc.strerror) self._output(msg) return self._output("Saved +%d bytes to %s" % (len(mem), filename)) def help_fill(self): self._output("fill <address_range> <data_list>") self._output("Fill memory in the address range with the data in") self._output("<data_list>. If the size of the address range is") self._output("greater than the size of the data_list, the data_list ") self._output("is repeated.") def do_fill(self, args): split = shlex.split(args) if len(split) < 2: return self.help_fill() try: start, end = self._address_parser.range(split[0]) filler = list(map(self._address_parser.number, split[1:])) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" else: self._fill(start, end, filler) def _fill(self, start, end, filler): address = start length, index = len(filler), 0 if start == end: end = start + length - 1 if (end > self.addrMask): end = self.addrMask while address <= end: address &= self.addrMask self._mpu.memory[address] = (filler[index] & self.byteMask) index += 1 if index == length: index = 0 address += 1 fmt = (end - start + 1, start, end) starttoend = "$" + self.addrFmt + " to $" + self.addrFmt self._output(("Wrote +%d bytes from " + starttoend) % fmt) def help_mem(self): self._output("mem <address_range>") self._output("Display the contents of memory.") self._output('Range is specified like "<start:end>".') def do_mem(self, args): split = shlex.split(args) if len(split) != 1: return self.help_mem() start, end = self._address_parser.range(split[0]) line = self.addrFmt % start + ":" for address in range(start, end + 1): byte = self._mpu.memory[address] more = " " + self.byteFmt % byte exceeded = len(line) + len(more) > self._width if exceeded: self._output(line) line = self.addrFmt % address + ":" line += more self._output(line) 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: self._output("Syntax error: %s" % args) return self.help_add_label() try: address = self._address_parser.number(split[0]) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" except OverflowError: self._output("Overflow error: %s" % args) else: label = split[1] 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 = list(self._address_parser.labels.values()) keys = list(self._address_parser.labels.keys()) byaddress = list(zip(values, keys)) byaddress.sort() for address, label in byaddress: self._output(self.addrFmt % 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): if args == '': return self.help_delete_label() if args in self._address_parser.labels: del self._address_parser.labels[args] def do_width(self, args): if args != '': try: new_width = int(args) if new_width >= 10: self._width = new_width else: self._output("Minimum terminal width is 10") except ValueError: self._output("Illegal width: %s" % args) self._output("Terminal width is %d" % self._width) def help_width(self): self._output("width <columns>") self._output("Set the width used by some commands to wrap output.") self._output("With no argument, the current width is printed.") def do_add_breakpoint(self, args): split = shlex.split(args) if len(split) != 1: self._output("Syntax error: %s" % args) return self.help_add_breakpoint() address = self._address_parser.number(split[0]) if address in self._breakpoints: self._output("Breakpoint already present at $%04X" % address) else: self._breakpoints.append(address) msg = "Breakpoint %d added at $%04X" self._output(msg % (len(self._breakpoints) - 1, address)) def help_add_breakpoint(self): self._output("add_breakpoint <address|label>") self._output("Add a breakpoint on execution at the given address or label") def do_delete_breakpoint(self, args): split = shlex.split(args) if len(split) != 1: self._output("Syntax error: %s" % args) return self.help_delete_breakpoint() number = None try: number = int(split[0]) if number < 0 or number > len(self._breakpoints): self._output("Invalid breakpoint number %d", number) return except ValueError: self._output("Illegal number: %s" % args) return if self._breakpoints[number] is not None: self._breakpoints[number] = None self._output("Breakpoint %d removed" % number) else: self._output("Breakpoint %d already removed" % number) def help_delete_breakpoint(self): self._output("delete_breakpoint <number>") self._output("Delete the breakpoint on execution marked by the given number") def do_show_breakpoints(self, args): for i, address in enumerate(self._breakpoints): if address is not None: bpinfo = "Breakpoint %d: $%04X" % (i, address) label = self._address_parser.label_for(address) if label is not None: bpinfo += " " + label self._output(bpinfo) def help_show_breakpoints(self): self._output("show_breakpoints") self._output("Lists the currently assigned breakpoints")
def _reset(self, mpu_type): self._mpu = mpu_type() self._install_mpu_observers() self._address_parser = AddressParser() self._disassembler = Disassembler(self._mpu, self._address_parser) self._assembler = Assembler(self._mpu, self._address_parser)
class Monitor(cmd.Cmd): def __init__(self, mpu_type=NMOS6502, completekey='tab', stdin=None, stdout=None): self._reset(mpu_type) self._width = 78 self._update_prompt() self._add_shortcuts() cmd.Cmd.__init__(self, completekey, stdin, stdout) def onecmd(self, line): line = self._preprocess_line(line) result = None try: result = cmd.Cmd.onecmd(self, line) except KeyboardInterrupt: self._output("Interrupt") except Exception as e: (file, fun, line), t, v, tbinfo = compact_traceback() error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) self._output(error) self._update_prompt() return result def _reset(self, mpu_type): self._mpu = mpu_type() self._install_mpu_observers() self._address_parser = AddressParser() self._disassembler = Disassembler(self._mpu, self._address_parser) self._assembler = Assembler(self._mpu, self._address_parser) def _add_shortcuts(self): self._shortcuts = {'~': 'tilde', '?': 'help', 'a': 'assemble', 'al': 'add_label', 'd': 'disassemble', 'dl': 'delete_label', 'f': 'fill', '>': 'fill', 'g': 'goto', 'l': 'load', 'm': 'mem', 'r': 'registers', 'ret': 'return', 'rad': 'radix', 's': 'save', 'shl': 'show_labels', 'x': 'quit', 'z': 'step'} def _preprocess_line(self, line): # line comments quoted = False for pos, char in enumerate(line): if char in ('"', "'"): quoted = not quoted if (not quoted) and (char == ';'): line = line[:pos] break # whitespace & leading dots line = line.strip(' \t').lstrip('.') # special case for vice compatibility if line.startswith('~'): line = self._shortcuts['~'] + ' ' + line[1:] # command shortcuts for shortcut, command in self._shortcuts.items(): if line == shortcut: line = command break pattern = '^%s\s+' % re.escape(shortcut) matches = re.match(pattern, line) if matches: start, end = matches.span() line = "%s %s" % (command, line[end:]) break return line def _install_mpu_observers(self): def putc(address, value): self.stdout.write(chr(value)) self.stdout.flush() def getc(address): char = console.getch_noblock(self.stdin) if char: byte = ord(char) else: byte = 0 return byte m = ObservableMemory() #m.subscribe_to_write([0xF001], putc) #m.subscribe_to_read([0xF004], getc) self._mpu.memory = m def _update_prompt(self): self.prompt = "\n%s\n." % repr(self._mpu) def _output(self, stuff): if stuff is not None: self.stdout.write(stuff + "\n") def do_help(self, args): args = self._shortcuts.get(args.strip(), args) return cmd.Cmd.do_help(self, args) def help_version(self): self._output("version\t\tDisplay Py65 version information.") def do_version(self, args): self._output("\nPy65 Monitor") def help_help(self): self._output("help\t\tPrint a list of available actions.") self._output("help <action>\tPrint help for <action>.") def help_reset(self): self._output("reset\t\tReset the microprocessor") def do_reset(self, args): klass = self._mpu.__class__ self._reset(mpu_type=klass) def do_mpu(self, args): mpus = {'6502': NMOS6502, '65C02': CMOS65C02} def available_mpus(): mpu_list = ', '.join(list(mpus.keys())) self._output("Available MPUs: %s" % mpu_list) if args == '': self._output("Current MPU is %s" % self._mpu.name) available_mpus() else: requested = args.upper() new_mpu = mpus.get(requested, None) if new_mpu is None: self._output("Unknown MPU: %s" % args) available_mpus() else: self._reset(new_mpu) self._output("Reset with new MPU %s" % self._mpu.name) def help_mpu(self): self._output("mpu\t\tPrint available microprocessors.") self._output("mpu <type>\tSelect a new microprocessor.") def do_EOF(self, args): self._output('') return 1 def help_EOF(self): self._output("To quit, type ^D or use the quit command.") def do_quit(self, args): return self.do_EOF(args) def help_quit(self): return self.help_EOF() def do_assemble(self, args): split = args.split(None, 1) if len(split) != 2: return self._interactive_assemble(args) start, statement = split try: start = self._address_parser.number(start) except KeyError: self._output("Bad label: %s" % start) return bytes = self._assembler.assemble(statement, start) if bytes is None: self._output("Assemble failed: %s" % statement) else: end = start + len(bytes) self._mpu.memory[start:end] = bytes self.do_disassemble('%04x:%04x' % (start, end)) def help_assemble(self): self._output("assemble <address> <statement>") self._output("Assemble a statement at the address.") def _interactive_assemble(self, args): if args == '': start = self._mpu.pc else: try: start = self._address_parser.number(args) except KeyError: self._output("Bad label: %s" % start) return assembling = True while assembling: prompt = "\r$%04x " % (start) line = console.line_input(prompt, stdin=self.stdin, stdout=self.stdout) if not line: self.stdout.write("\n") return # assemble into memory bytes = self._assembler.assemble(line) if bytes is None: self.stdout.write("\r$%04x ???\n" % start) continue end = start + len(bytes) self._mpu.memory[start:end] = bytes # print disassembly bytes, disasm = self._disassembler.instruction_at(start) disassembly = self._format_disassembly(start, bytes, disasm) self.stdout.write("\r" + (' ' * (len(prompt+line) + 5) ) + "\r") self.stdout.write(disassembly + "\n") start += bytes 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) self._output(self._format_disassembly(address, bytes, disasm)) address += bytes def _format_disassembly(self, address, bytes, disasm): mem = '' for byte in self._mpu.memory[address:address+bytes]: mem += '%02x ' % byte return "$%04x %-10s%s" % (address, mem, disasm) def help_disassemble(self): self._output("disassemble <address_range>") self._output("Disassemble instructions in the address range.") def help_step(self): self._output("step") 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("return") self._output("Continues execution and returns to the monitor just") self._output("before the next RTS or RTI is executed.") def do_return(self, args): 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 self._run(stopcodes=brks) def _run(self, stopcodes=[]): last_instruct = None while last_instruct not in stopcodes: self._mpu.step() last_instruct = self._mpu.memory[self._mpu.pc] def help_radix(self): self._output("radix [H|D|O|B]") self._output("Set the default radix to hex, decimal, octal, or binary.") self._output("With no argument, the current radix is printed.") def help_cycles(self): self._output("Display the total number of cycles executed.") def do_cycles(self, args): self._output(str(self._mpu.processorCycles)) def do_radix(self, args): radixes = {'Hexadecimal': 16, 'Decimal': 10, 'Octal': 8, 'Binary': 2} if args != '': new = args[0].lower() changed = False for name, radix in radixes.items(): if name[0].lower() == new: self._address_parser.radix = radix changed = True if not changed: self._output("Illegal radix: %s" % args) for name, radix in radixes.items(): if self._address_parser.radix == radix: self._output("Default radix is %s" % name) def help_tilde(self): self._output("~ <number>") self._output("Display the specified number in decimal, hex, octal, and binary.") def do_tilde(self, args): try: num = self._address_parser.number(args) except ValueError: self._output("Syntax error: %s" % args) return self._output("+%u" % num) self._output("$%02x" % num) self._output("%04o" % num) self._output(itoa(num, 2).zfill(8)) def help_registers(self): self._output("registers[<reg_name> = <number> [, <reg_name> = <number>]*]") self._output("Assign respective registers. With no parameters,") self._output("display register values.") def do_registers(self, args): if args == '': return pairs = re.findall('([^=,\s]*)=([^=,\s]*)', args) if pairs == []: return self._output("Syntax error: %s" % args) for register, value in pairs: if register not in ('pc', 'sp', 'a', 'x', 'y', 'p'): self._output("Invalid register: %s" % register) else: try: intval = self._address_parser.number(value) & 0xFFFF if len(register) == 1: intval &= 0xFF setattr(self._mpu, register, intval) except KeyError as why: self._output(str(why)) def help_cd(self, args): self._output("cd <directory>") self._output("Change the working directory.") def do_cd(self, args): try: os.chdir(args) except OSError as why: msg = "Cannot change directory: [%d] %s" % (why.errno, why.strerror) self._output(msg) self.do_pwd() def help_pwd(self): self._output("Show the current working directory.") def do_pwd(self, args=None): cwd = os.getcwd() self._output(cwd) def help_load(self): self._output("load \"filename\" <address>") self._output("Load the specified file into memory at the specified address.") self._output("Commodore-style load address bytes are ignored.") def do_load(self, args): split = shlex.split(args) if len(split) > 2: self._output("Syntax error: %s" % args) return filename = split[0] if len(split) == 2: start = self._address_parser.number(split[1]) else: start = self._mpu.pc try: f = open(filename, 'rb') bytes = f.read() f.close() except (OSError, IOError) as why: msg = "Cannot load file: [%d] %s" % (why[0], why[1]) self._output(msg) return # self._fill(start, start, list(map(ord, bytes))) self._fill(start, start, list(bytes)) def do_save(self, args): split = shlex.split(args) if len(split) != 3: self._output("Syntax error: %s" % args) return filename = split[0] start = self._address_parser.number(split[1]) end = self._address_parser.number(split[2]) bytes = bytearray(self._mpu.memory[start:end+1]) try: f = open(filename, 'wb') f.write(bytes) f.close() except (OSError, IOError) as why: msg = "Cannot save file: [%d] %s" % (why.errno, why.strerror) self._output(msg) return self._output("Saved +%d bytes to %s" % (len(bytes), filename)) def help_save(self): self._output("save \"filename\" <start> <end>") self._output("Save the specified memory range to disk as a binary file.") self._output("Commodore-style load address bytes are not written.") def help_fill(self): self._output("fill <address_range> <data_list>") self._output("Fill memory in the specified address range with the data in") self._output("<data_list>. If the size of the address range is greater") self._output("than the size of the data_list, the data_list is repeated.") def do_fill(self, args): split = shlex.split(args) if len(split) < 2: self._output("Syntax error: %s" % args) return start, end = self._address_parser.range(split[0]) filler = list(map(self._address_parser.number, split[1:])) self._fill(start, end, filler) def _fill(self, start, end, filler): address = start length, index = len(filler), 0 if start == end: end = start + length - 1 if (end > 0xFFFF): end = 0xFFFF while address <= end: address &= 0xFFFF self._mpu.memory[address] = (filler[index] & 0xFF) index += 1 if index == length: index = 0 address += 1 fmt = (end - start + 1, start, end) self._output("Wrote +%d bytes from $%04x to $%04x" % fmt) def help_mem(self): self._output("mem <address_range>") self._output("Display the contents of memory.") def do_mem(self, args): start, end = self._address_parser.range(args) line = "%04x:" % start for address in range(start, end+1): byte = self._mpu.memory[address] more = " %02x" % byte exceeded = len(line) + len(more) > self._width if exceeded: self._output(line) line = "%04x:" % address line += more self._output(line) 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: self._output("Syntax error: %s" % args) return address = self._address_parser.number(split[0]) label = split[1] 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 = list(self._address_parser.labels.values()) keys = list(self._address_parser.labels.keys()) byaddress = list(zip(values, keys)) byaddress.sort() 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): if args == '': return self.help_delete_label() try: del self._address_parser.labels[args] except KeyError: pass def do_width(self, args): if args != '': try: new_width = int(args) if new_width >= 10: self._width = new_width else: self._output("Minimum terminal width is 10") except ValueError: self._output("Illegal width: %s" % args) self._output("Terminal width is %d" % self._width) def help_width(self): self._output("width <columns>") self._output("Set the width used by some commands to wrap output.") self._output("With no argument, the current width is printed.")
class Monitor(cmd.Cmd): Microprocessors = {'6502': NMOS6502, '65C02': CMOS65C02, '65Org16': V65Org16} def __init__(self, argv=None, stdin=None, stdout=None, mpu_type=NMOS6502, memory=None, putc_addr=0xF001, getc_addr=0xF004): self.mpu_type = mpu_type self.memory = memory self.putc_addr = putc_addr self.getc_addr = getc_addr self._breakpoints = [] self._width = 78 self.prompt = "." self._add_shortcuts() cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout) if argv is None: argv = sys.argv load, rom, goto = self._parse_args(argv) self._reset(self.mpu_type, self.getc_addr, self.putc_addr) if load is not None: self.do_load(load) if goto is not None: self.do_goto(goto) if rom is not None: # load a ROM and run from the reset vector self.do_load("%r top" % rom) physMask = self._mpu.memory.physMask reset = self._mpu.RESET & physMask dest = self._mpu.memory[reset] + \ (self._mpu.memory[reset + 1] << self.byteWidth) self.do_goto("$%x" % dest) def _parse_args(self, argv): try: shortopts = 'hi:o:m:l:r:g:' longopts = ['help', 'mpu=', 'input=', 'output=', 'load=', 'rom=', 'goto='] options, args = getopt.getopt(argv[1:], shortopts, longopts) except getopt.GetoptError as exc: self._output(exc.args[0]) self._usage() self._exit(1) load, rom, goto = None, None, None for opt, value in options: if opt in ('-i', '--input'): self.getc_addr = int(value, 16) if opt in ('-o', '--output'): self.putc_addr = int(value, 16) if opt in ('-m', '--mpu'): mpu_type = self._get_mpu(value) if mpu_type is None: mpus = sorted(self.Microprocessors.keys()) msg = "Fatal: no such MPU. Available MPUs: %s" self._output(msg % ', '.join(mpus)) sys.exit(1) self.mpu_type = mpu_type if opt in ("-h", "--help"): self._usage() self._exit(0) if opt in ('-l', '--load'): load = value if opt in ('-r', '--rom'): rom = value if opt in ('-g', '--goto'): goto = value return load, rom, goto def _usage(self): usage = __doc__ % sys.argv[0] self._output(usage) def onecmd(self, line): line = self._preprocess_line(line) result = None try: result = cmd.Cmd.onecmd(self, line) except KeyboardInterrupt: self._output("Interrupt") except Exception: (file, fun, line), t, v, tbinfo = compact_traceback() error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) self._output(error) if not line.startswith("quit"): self._output_mpu_status() return result def _reset(self, mpu_type, getc_addr=0xF004, putc_addr=0xF001): self._mpu = mpu_type(memory=self.memory) self.addrWidth = self._mpu.ADDR_WIDTH self.byteWidth = self._mpu.BYTE_WIDTH self.addrFmt = self._mpu.ADDR_FORMAT self.byteFmt = self._mpu.BYTE_FORMAT self.addrMask = self._mpu.addrMask self.byteMask = self._mpu.byteMask if getc_addr and putc_addr: self._install_mpu_observers(getc_addr, putc_addr) self._address_parser = AddressParser() self._disassembler = Disassembler(self._mpu, self._address_parser) self._assembler = Assembler(self._mpu, self._address_parser) def _add_shortcuts(self): self._shortcuts = {'EOF': 'quit', '~': 'tilde', 'a': 'assemble', 'ab': 'add_breakpoint', 'al': 'add_label', 'd': 'disassemble', 'db': 'delete_breakpoint', 'dl': 'delete_label', 'exit': 'quit', 'f': 'fill', '>': 'fill', 'g': 'goto', 'h': 'help', '?': 'help', 'l': 'load', 'm': 'mem', 'q': 'quit', 'r': 'registers', 'ret': 'return', 'rad': 'radix', 's': 'save', 'shb': 'show_breakpoints', 'shl': 'show_labels', 'x': 'quit', 'z': 'step'} def _preprocess_line(self, line): # line comments quoted = False for pos, char in enumerate(line): if char in ('"', "'"): quoted = not quoted if (not quoted) and (char == ';'): line = line[:pos] break # whitespace & leading dots line = line.strip(' \t').lstrip('.') # special case for vice compatibility if line.startswith('~'): line = self._shortcuts['~'] + ' ' + line[1:] # command shortcuts for shortcut, command in self._shortcuts.items(): if line == shortcut: line = command break pattern = '^%s\s+' % re.escape(shortcut) matches = re.match(pattern, line) if matches: start, end = matches.span() line = "%s %s" % (command, line[end:]) break return line def _get_mpu(self, name): requested = name.lower() mpu = None for key, klass in self.Microprocessors.items(): if key.lower() == requested: mpu = klass break return mpu def _install_mpu_observers(self, getc_addr, putc_addr): def putc(address, value): try: self.stdout.write(chr(value)) except UnicodeEncodeError: # Python 3 self.stdout.write("?") self.stdout.flush() def getc(address): char = console.getch_noblock(self.stdin) if char: byte = ord(char) else: byte = 0 return byte m = ObservableMemory(subject=self.memory, addrWidth=self.addrWidth) m.subscribe_to_write([self.putc_addr], putc) m.subscribe_to_read([self.getc_addr], getc) self._mpu.memory = m def _output_mpu_status(self): self._output("\n" + repr(self._mpu)) def _output(self, stuff): self.stdout.write("%s\n" % stuff) def _exit(self, exitcode=0): sys.exit(exitcode) def do_help(self, args): args = self._shortcuts.get(args.strip(), args) return cmd.Cmd.do_help(self, args) def help_version(self): self._output("version\t\tDisplay Py65 version information.") def do_version(self, args): self._output("\nPy65 Monitor") def help_help(self): self._output("help\t\tPrint a list of available actions.") self._output("help <action>\tPrint help for <action>.") def help_reset(self): self._output("reset\t\tReset the microprocessor") def do_reset(self, args): klass = self._mpu.__class__ self._reset(mpu_type=klass) def do_mpu(self, args): def available_mpus(): mpus = list(self.Microprocessors.keys()) mpus.sort() self._output("Available MPUs: %s" % ', '.join(mpus)) if args == '': self._output("Current MPU is %s" % self._mpu.name) available_mpus() else: new_mpu = self._get_mpu(args) if new_mpu is None: self._output("Unknown MPU: %s" % args) available_mpus() else: self._reset(new_mpu,self.getc_addr,self.putc_addr) self._output("Reset with new MPU %s" % self._mpu.name) def help_mpu(self): self._output("mpu\t\tPrint available microprocessors.") self._output("mpu <type>\tSelect a new microprocessor.") def do_quit(self, args): self._output('') return 1 def help_quit(self): self._output("To quit, type ^D or use the quit command.") def do_assemble(self, args): splitted = args.split(None, 1) if len(splitted) != 2: return self._interactive_assemble(args) statement = splitted[1] try: start = self._address_parser.number(splitted[0]) bytes = self._assembler.assemble(statement, start) end = start + len(bytes) self._mpu.memory[start:end] = bytes self.do_disassemble(self.addrFmt % start) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" except OverflowError: self._output("Overflow error: %s" % args) except SyntaxError: self._output("Syntax error: %s" % statement) def help_assemble(self): self._output("assemble\t\t\t" "Start interactive assembly at the program counter.") self._output("assemble <address>\t\t" "Start interactive assembly at the address.") self._output("assemble <address> <statement>\t" "Assemble a statement at the address.") def _interactive_assemble(self, args): if args == '': start = self._mpu.pc else: try: start = self._address_parser.number(args) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" return while True: prompt = "\r$" + (self.addrFmt % start) + " " + \ (" " * int(1 + self.byteWidth / 4) * 3) line = console.line_input(prompt, stdin=self.stdin, stdout=self.stdout) if not line.strip(): self.stdout.write("\n") return # assemble into memory try: bytes = self._assembler.assemble(line, pc=start) numbytes = len(bytes) end = start + numbytes self._mpu.memory[start:end] = bytes # print disassembly _, disasm = self._disassembler.instruction_at(start) fdisasm = self._format_disassembly(start, numbytes, disasm) indent = ' ' * (len(prompt + line) + 5) self.stdout.write("\r" + indent + "\r") self.stdout.write(fdisasm + "\n") # advance to next address start += numbytes if start >= (2 ** self._mpu.ADDR_WIDTH): start = 0 except KeyError: addr = self.addrFmt % start self.stdout.write("\r$%s ?Label\n" % addr) except OverflowError: addr = self.addrFmt % start self.stdout.write("\r$%s ?Overflow\n" % addr) except SyntaxError: addr = self.addrFmt % start self.stdout.write("\r$%s ?Syntax\n" % addr) def do_disassemble(self, args): splitted = shlex.split(args) if len(splitted) != 1: return self.help_disassemble() address_parts = splitted[0].split(":") start = self._address_parser.number(address_parts[0]) if len(address_parts) > 1: end = self._address_parser.number(address_parts[1]) else: end = start max_address = (2 ** self._mpu.ADDR_WIDTH) - 1 cur_address = start needs_wrap = start > end while needs_wrap or cur_address <= end: length, disasm = self._disassembler.instruction_at(cur_address) self._output(self._format_disassembly(cur_address, length, disasm)) remaining = length while remaining: remaining -= 1 cur_address += 1 if start > end and cur_address > max_address: needs_wrap = False cur_address = 0 def _format_disassembly(self, address, length, disasm): cur_address = address max_address = (2 ** self._mpu.ADDR_WIDTH) - 1 bytes_remaining = length dump = '' while bytes_remaining: if cur_address > max_address: cur_address = 0 dump += self.byteFmt % self._mpu.memory[cur_address] + " " cur_address += 1 bytes_remaining -= 1 fieldwidth = 1 + int(1 + self.byteWidth / 4) * 3 fieldfmt = "%%-%ds" % fieldwidth return "$" + self.addrFmt % address + " " + fieldfmt % dump + disasm def help_disassemble(self): self._output("disassemble <address_range>") self._output("Disassemble instructions in the address range.") self._output('Range is specified like "<start>:<end>".') def help_step(self): self._output("step") self._output("Single-step through instructions.") def do_step(self, args): self._mpu.step() self.do_disassemble(self.addrFmt % self._mpu.pc) def help_return(self): self._output("return") self._output("Continues execution and returns to the monitor just") self._output("before the next RTS or RTI is executed.") def do_return(self, args): 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): if args == '': return self.help_goto() self._mpu.pc = self._address_parser.number(args) brks = [0x00] # BRK self._run(stopcodes=brks) def _run(self, stopcodes): stopcodes = set(stopcodes) breakpoints = set(self._breakpoints) mpu = self._mpu mem = self._mpu.memory if not breakpoints: while True: mpu.step() if mem[mpu.pc] in stopcodes: break else: while True: mpu.step() pc = mpu.pc if mem[pc] in stopcodes: break if pc in breakpoints: msg = "Breakpoint %d reached." self._output(msg % self._breakpoints.index(pc)) break def help_radix(self): self._output("radix [H|D|O|B]") self._output("Set default radix to hex, decimal, octal, or binary.") self._output("With no argument, the current radix is printed.") def help_cycles(self): self._output("Display the total number of cycles executed.") def do_cycles(self, args): self._output(str(self._mpu.processorCycles)) def do_radix(self, args): radixes = {'Hexadecimal': 16, 'Decimal': 10, 'Octal': 8, 'Binary': 2} if args != '': new = args[0].lower() changed = False for name, radix in radixes.items(): if name[0].lower() == new: self._address_parser.radix = radix changed = True if not changed: self._output("Illegal radix: %s" % args) for name, radix in radixes.items(): if self._address_parser.radix == radix: self._output("Default radix is %s" % name) def help_tilde(self): self._output("~ <number>") self._output("Display a number in decimal, hex, octal, and binary.") def do_tilde(self, args): if args == '': return self.help_tilde() try: num = self._address_parser.number(args) self._output("+%u" % num) self._output("$" + self.byteFmt % num) self._output("%04o" % num) self._output(itoa(num, 2).zfill(8)) except KeyError: self._output("Bad label: %s" % args) except OverflowError: self._output("Overflow error: %s" % args) def help_registers(self): self._output("registers[<name>=<value> [, <name>=<value>]*]") self._output("Assign respective registers. With no parameters,") self._output("display register values.") def do_registers(self, args): if args == '': return pairs = re.findall('([^=,\s]*)=([^=,\s]*)', args) if pairs == []: return self._output("Syntax error: %s" % args) for register, value in pairs: if register not in ('pc', 'sp', 'a', 'x', 'y', 'p'): self._output("Invalid register: %s" % register) else: try: intval = self._address_parser.number(value) except KeyError as exc: # label not found self._output(exc.args[0]) continue except OverflowError as exc: # wider than address space msg = "Overflow: %r too wide for register %r" self._output(msg % (value, register)) continue if register != 'pc': if intval != (intval & self.byteMask): msg = "Overflow: %r too wide for register %r" self._output(msg % (value, register)) continue setattr(self._mpu, register, intval) def help_cd(self): self._output("cd <directory>") self._output("Change the working directory.") def do_cd(self, args): if args == '': return self.help_cd() try: os.chdir(args) except OSError as exc: msg = "Cannot change directory: [%d] %s" % (exc.errno, exc.strerror) self._output(msg) self.do_pwd() def help_pwd(self): self._output("Show the current working directory.") def do_pwd(self, args=None): cwd = os.getcwd() self._output(cwd) def help_load(self): self._output("load <filename|url> <address|top>") self._output("Load a file into memory at the specified address.") self._output('An address of "top" loads into the top of memory.') self._output("Commodore-style load address bytes are ignored.") def do_load(self, args): split = shlex.split(args) if len(split) not in (1, 2): self._output("Syntax error: %s" % args) return filename = split[0] if "://" in filename: try: f = urlopen(filename) bytes = f.read() f.close() except Exception as exc: msg = "Cannot fetch remote file: %s" % str(exc) self._output(msg) return else: try: f = open(filename, 'rb') bytes = f.read() f.close() except (OSError, IOError) as exc: msg = "Cannot load file: [%d] %s" % (exc.errno, exc.strerror) self._output(msg) return if len(split) == 2: if split[1] == "top": # load a ROM to top of memory top_address = self.addrMask program_size = len(bytes) // (self.byteWidth // 8) start = top_address - program_size + 1 else: start = self._address_parser.number(split[1]) else: start = self._mpu.pc if self.byteWidth == 8: if isinstance(bytes, str): bytes = map(ord, bytes) else: # Python 3 bytes = [ b for b in bytes ] elif self.byteWidth == 16: def format(msb, lsb): if isinstance(bytes, str): return (ord(msb) << 8) + ord(lsb) else: # Python 3 return (msb << 8) + lsb bytes = list(map(format, bytes[0::2], bytes[1::2])) self._fill(start, start, bytes) def help_save(self): self._output("save \"filename\" <start> <end>") self._output("Save the specified memory range as a binary file.") self._output("Commodore-style load address bytes are not written.") def do_save(self, args): split = shlex.split(args) if len(split) != 3: self._output("Syntax error: %s" % args) return filename = split[0] start = self._address_parser.number(split[1]) end = self._address_parser.number(split[2]) mem = self._mpu.memory[start:end + 1] try: f = open(filename, 'wb') for m in mem: # output each octect from msb first for shift in range(self.byteWidth - 8, -1, -8): f.write(bytearray([(m >> shift) & 0xff])) f.close() except (OSError, IOError) as exc: msg = "Cannot save file: [%d] %s" % (exc.errno, exc.strerror) self._output(msg) return self._output("Saved +%d bytes to %s" % (len(mem), filename)) def help_fill(self): self._output("fill <address_range> <data_list>") self._output("Fill memory in the address range with the data in") self._output("<data_list>. If the size of the address range is") self._output("greater than the size of the data_list, the data_list ") self._output("is repeated.") def do_fill(self, args): split = shlex.split(args) if len(split) < 2: return self.help_fill() try: start, end = self._address_parser.range(split[0]) filler = list(map(self._address_parser.number, split[1:])) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" else: self._fill(start, end, filler) def _fill(self, start, end, filler): address = start length, index = len(filler), 0 if start == end: end = start + length - 1 if (end > self.addrMask): end = self.addrMask while address <= end: address &= self.addrMask self._mpu.memory[address] = (filler[index] & self.byteMask) index += 1 if index == length: index = 0 address += 1 fmt = (end - start + 1, start, end) starttoend = "$" + self.addrFmt + " to $" + self.addrFmt self._output(("Wrote +%d bytes from " + starttoend) % fmt) def help_mem(self): self._output("mem <address_range>") self._output("Display the contents of memory.") self._output('Range is specified like "<start:end>".') def do_mem(self, args): split = shlex.split(args) if len(split) != 1: return self.help_mem() start, end = self._address_parser.range(split[0]) line = self.addrFmt % start + ":" for address in range(start, end + 1): byte = self._mpu.memory[address] more = " " + self.byteFmt % byte exceeded = len(line) + len(more) > self._width if exceeded: self._output(line) line = self.addrFmt % address + ":" line += more self._output(line) 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: self._output("Syntax error: %s" % args) return self.help_add_label() try: address = self._address_parser.number(split[0]) except KeyError as exc: self._output(exc.args[0]) # "Label not found: foo" except OverflowError: self._output("Overflow error: %s" % args) else: label = split[1] 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 = list(self._address_parser.labels.values()) keys = list(self._address_parser.labels.keys()) byaddress = list(zip(values, keys)) byaddress.sort() for address, label in byaddress: self._output(self.addrFmt % 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): if args == '': return self.help_delete_label() if args in self._address_parser.labels: del self._address_parser.labels[args] def do_width(self, args): if args != '': try: new_width = int(args) if new_width >= 10: self._width = new_width else: self._output("Minimum terminal width is 10") except ValueError: self._output("Illegal width: %s" % args) self._output("Terminal width is %d" % self._width) def help_width(self): self._output("width <columns>") self._output("Set the width used by some commands to wrap output.") self._output("With no argument, the current width is printed.") def do_add_breakpoint(self, args): split = shlex.split(args) if len(split) != 1: self._output("Syntax error: %s" % args) return self.help_add_breakpoint() address = self._address_parser.number(split[0]) if address in self._breakpoints: self._output("Breakpoint already present at $%04X" % address) else: self._breakpoints.append(address) msg = "Breakpoint %d added at $%04X" self._output(msg % (len(self._breakpoints) - 1, address)) def help_add_breakpoint(self): self._output("add_breakpoint <address|label>") self._output("Add a breakpoint on execution at the given address or label") def do_delete_breakpoint(self, args): split = shlex.split(args) if len(split) != 1: self._output("Syntax error: %s" % args) return self.help_delete_breakpoint() number = None try: number = int(split[0]) if number < 0 or number > len(self._breakpoints): self._output("Invalid breakpoint number %d", number) return except ValueError: self._output("Illegal number: %s" % args) return if self._breakpoints[number] is not None: self._breakpoints[number] = None self._output("Breakpoint %d removed" % number) else: self._output("Breakpoint %d already removed" % number) def help_delete_breakpoint(self): self._output("delete_breakpoint <number>") self._output("Delete the breakpoint on execution marked by the given number") def do_show_breakpoints(self, args): for i, address in enumerate(self._breakpoints): if address is not None: bpinfo = "Breakpoint %d: $%04X" % (i, address) label = self._address_parser.label_for(address) if label is not None: bpinfo += " " + label self._output(bpinfo) def help_show_breakpoints(self): self._output("show_breakpoints") self._output("Lists the currently assigned breakpoints")
class Monitor(cmd.Cmd): def __init__(self, mpu_type=NMOS6502, completekey='tab', stdin=None, stdout=None): self._reset(mpu_type) self._width = 78 self._update_prompt() self._add_shortcuts() cmd.Cmd.__init__(self, completekey, stdin, stdout) def onecmd(self, line): line = self._preprocess_line(line) result = None try: result = cmd.Cmd.onecmd(self, line) except KeyboardInterrupt: self._output("Interrupt") except Exception as e: (file, fun, line), t, v, tbinfo = compact_traceback() error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) self._output(error) self._update_prompt() return result def _reset(self, mpu_type): self._mpu = mpu_type() self._install_mpu_observers() self._address_parser = AddressParser() self._disassembler = Disassembler(self._mpu, self._address_parser) self._assembler = Assembler(self._mpu, self._address_parser) def _add_shortcuts(self): self._shortcuts = { '~': 'tilde', '?': 'help', 'a': 'assemble', 'al': 'add_label', 'd': 'disassemble', 'dl': 'delete_label', 'f': 'fill', '>': 'fill', 'g': 'goto', 'l': 'load', 'm': 'mem', 'r': 'registers', 'ret': 'return', 'rad': 'radix', 's': 'save', 'shl': 'show_labels', 'x': 'quit', 'z': 'step' } def _preprocess_line(self, line): # line comments quoted = False for pos, char in enumerate(line): if char in ('"', "'"): quoted = not quoted if (not quoted) and (char == ';'): line = line[:pos] break # whitespace & leading dots line = line.strip(' \t').lstrip('.') # special case for vice compatibility if line.startswith('~'): line = self._shortcuts['~'] + ' ' + line[1:] # command shortcuts for shortcut, command in self._shortcuts.items(): if line == shortcut: line = command break pattern = '^%s\s+' % re.escape(shortcut) matches = re.match(pattern, line) if matches: start, end = matches.span() line = "%s %s" % (command, line[end:]) break return line def _install_mpu_observers(self): def putc(address, value): self.stdout.write(chr(value)) self.stdout.flush() def getc(address): char = console.getch_noblock(self.stdin) if char: byte = ord(char) else: byte = 0 return byte m = ObservableMemory() #m.subscribe_to_write([0xF001], putc) #m.subscribe_to_read([0xF004], getc) self._mpu.memory = m def _update_prompt(self): self.prompt = "\n%s\n." % repr(self._mpu) def _output(self, stuff): if stuff is not None: self.stdout.write(stuff + "\n") def do_help(self, args): args = self._shortcuts.get(args.strip(), args) return cmd.Cmd.do_help(self, args) def help_version(self): self._output("version\t\tDisplay Py65 version information.") def do_version(self, args): self._output("\nPy65 Monitor") def help_help(self): self._output("help\t\tPrint a list of available actions.") self._output("help <action>\tPrint help for <action>.") def help_reset(self): self._output("reset\t\tReset the microprocessor") def do_reset(self, args): klass = self._mpu.__class__ self._reset(mpu_type=klass) def do_mpu(self, args): mpus = {'6502': NMOS6502, '65C02': CMOS65C02} def available_mpus(): mpu_list = ', '.join(list(mpus.keys())) self._output("Available MPUs: %s" % mpu_list) if args == '': self._output("Current MPU is %s" % self._mpu.name) available_mpus() else: requested = args.upper() new_mpu = mpus.get(requested, None) if new_mpu is None: self._output("Unknown MPU: %s" % args) available_mpus() else: self._reset(new_mpu) self._output("Reset with new MPU %s" % self._mpu.name) def help_mpu(self): self._output("mpu\t\tPrint available microprocessors.") self._output("mpu <type>\tSelect a new microprocessor.") def do_EOF(self, args): self._output('') return 1 def help_EOF(self): self._output("To quit, type ^D or use the quit command.") def do_quit(self, args): return self.do_EOF(args) def help_quit(self): return self.help_EOF() def do_assemble(self, args): split = args.split(None, 1) if len(split) != 2: return self._interactive_assemble(args) start, statement = split try: start = self._address_parser.number(start) except KeyError: self._output("Bad label: %s" % start) return bytes = self._assembler.assemble(statement, start) if bytes is None: self._output("Assemble failed: %s" % statement) else: end = start + len(bytes) self._mpu.memory[start:end] = bytes self.do_disassemble('%04x:%04x' % (start, end)) def help_assemble(self): self._output("assemble <address> <statement>") self._output("Assemble a statement at the address.") def _interactive_assemble(self, args): if args == '': start = self._mpu.pc else: try: start = self._address_parser.number(args) except KeyError: self._output("Bad label: %s" % start) return assembling = True while assembling: prompt = "\r$%04x " % (start) line = console.line_input(prompt, stdin=self.stdin, stdout=self.stdout) if not line: self.stdout.write("\n") return # assemble into memory bytes = self._assembler.assemble(line) if bytes is None: self.stdout.write("\r$%04x ???\n" % start) continue end = start + len(bytes) self._mpu.memory[start:end] = bytes # print disassembly bytes, disasm = self._disassembler.instruction_at(start) disassembly = self._format_disassembly(start, bytes, disasm) self.stdout.write("\r" + (' ' * (len(prompt + line) + 5)) + "\r") self.stdout.write(disassembly + "\n") start += bytes 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) self._output(self._format_disassembly(address, bytes, disasm)) address += bytes def _format_disassembly(self, address, bytes, disasm): mem = '' for byte in self._mpu.memory[address:address + bytes]: mem += '%02x ' % byte return "$%04x %-10s%s" % (address, mem, disasm) def help_disassemble(self): self._output("disassemble <address_range>") self._output("Disassemble instructions in the address range.") def help_step(self): self._output("step") 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("return") self._output("Continues execution and returns to the monitor just") self._output("before the next RTS or RTI is executed.") def do_return(self, args): 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 self._run(stopcodes=brks) def _run(self, stopcodes=[]): last_instruct = None while last_instruct not in stopcodes: self._mpu.step() last_instruct = self._mpu.memory[self._mpu.pc] def help_radix(self): self._output("radix [H|D|O|B]") self._output( "Set the default radix to hex, decimal, octal, or binary.") self._output("With no argument, the current radix is printed.") def help_cycles(self): self._output("Display the total number of cycles executed.") def do_cycles(self, args): self._output(str(self._mpu.processorCycles)) def do_radix(self, args): radixes = {'Hexadecimal': 16, 'Decimal': 10, 'Octal': 8, 'Binary': 2} if args != '': new = args[0].lower() changed = False for name, radix in radixes.items(): if name[0].lower() == new: self._address_parser.radix = radix changed = True if not changed: self._output("Illegal radix: %s" % args) for name, radix in radixes.items(): if self._address_parser.radix == radix: self._output("Default radix is %s" % name) def help_tilde(self): self._output("~ <number>") self._output( "Display the specified number in decimal, hex, octal, and binary.") def do_tilde(self, args): try: num = self._address_parser.number(args) except ValueError: self._output("Syntax error: %s" % args) return self._output("+%u" % num) self._output("$%02x" % num) self._output("%04o" % num) self._output(itoa(num, 2).zfill(8)) def help_registers(self): self._output( "registers[<reg_name> = <number> [, <reg_name> = <number>]*]") self._output("Assign respective registers. With no parameters,") self._output("display register values.") def do_registers(self, args): if args == '': return pairs = re.findall('([^=,\s]*)=([^=,\s]*)', args) if pairs == []: return self._output("Syntax error: %s" % args) for register, value in pairs: if register not in ('pc', 'sp', 'a', 'x', 'y', 'p'): self._output("Invalid register: %s" % register) else: try: intval = self._address_parser.number(value) & 0xFFFF if len(register) == 1: intval &= 0xFF setattr(self._mpu, register, intval) except KeyError as why: self._output(str(why)) def help_cd(self, args): self._output("cd <directory>") self._output("Change the working directory.") def do_cd(self, args): try: os.chdir(args) except OSError as why: msg = "Cannot change directory: [%d] %s" % (why.errno, why.strerror) self._output(msg) self.do_pwd() def help_pwd(self): self._output("Show the current working directory.") def do_pwd(self, args=None): cwd = os.getcwd() self._output(cwd) def help_load(self): self._output("load \"filename\" <address>") self._output( "Load the specified file into memory at the specified address.") self._output("Commodore-style load address bytes are ignored.") def do_load(self, args): split = shlex.split(args) if len(split) > 2: self._output("Syntax error: %s" % args) return filename = split[0] if len(split) == 2: start = self._address_parser.number(split[1]) else: start = self._mpu.pc try: f = open(filename, 'rb') bytes = f.read() f.close() except (OSError, IOError) as why: msg = "Cannot load file: [%d] %s" % (why[0], why[1]) self._output(msg) return # self._fill(start, start, list(map(ord, bytes))) self._fill(start, start, list(bytes)) def do_save(self, args): split = shlex.split(args) if len(split) != 3: self._output("Syntax error: %s" % args) return filename = split[0] start = self._address_parser.number(split[1]) end = self._address_parser.number(split[2]) bytes = bytearray(self._mpu.memory[start:end + 1]) try: f = open(filename, 'wb') f.write(bytes) f.close() except (OSError, IOError) as why: msg = "Cannot save file: [%d] %s" % (why.errno, why.strerror) self._output(msg) return self._output("Saved +%d bytes to %s" % (len(bytes), filename)) def help_save(self): self._output("save \"filename\" <start> <end>") self._output( "Save the specified memory range to disk as a binary file.") self._output("Commodore-style load address bytes are not written.") def help_fill(self): self._output("fill <address_range> <data_list>") self._output( "Fill memory in the specified address range with the data in") self._output( "<data_list>. If the size of the address range is greater") self._output( "than the size of the data_list, the data_list is repeated.") def do_fill(self, args): split = shlex.split(args) if len(split) < 2: self._output("Syntax error: %s" % args) return start, end = self._address_parser.range(split[0]) filler = list(map(self._address_parser.number, split[1:])) self._fill(start, end, filler) def _fill(self, start, end, filler): address = start length, index = len(filler), 0 if start == end: end = start + length - 1 if (end > 0xFFFF): end = 0xFFFF while address <= end: address &= 0xFFFF self._mpu.memory[address] = (filler[index] & 0xFF) index += 1 if index == length: index = 0 address += 1 fmt = (end - start + 1, start, end) self._output("Wrote +%d bytes from $%04x to $%04x" % fmt) def help_mem(self): self._output("mem <address_range>") self._output("Display the contents of memory.") def do_mem(self, args): start, end = self._address_parser.range(args) line = "%04x:" % start for address in range(start, end + 1): byte = self._mpu.memory[address] more = " %02x" % byte exceeded = len(line) + len(more) > self._width if exceeded: self._output(line) line = "%04x:" % address line += more self._output(line) 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: self._output("Syntax error: %s" % args) return address = self._address_parser.number(split[0]) label = split[1] 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 = list(self._address_parser.labels.values()) keys = list(self._address_parser.labels.keys()) byaddress = list(zip(values, keys)) byaddress.sort() 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): if args == '': return self.help_delete_label() try: del self._address_parser.labels[args] except KeyError: pass def do_width(self, args): if args != '': try: new_width = int(args) if new_width >= 10: self._width = new_width else: self._output("Minimum terminal width is 10") except ValueError: self._output("Illegal width: %s" % args) self._output("Terminal width is %d" % self._width) def help_width(self): self._output("width <columns>") self._output("Set the width used by some commands to wrap output.") self._output("With no argument, the current width is printed.")
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)
class PY65Emu(object): def __init__(self): self.mpu = NMOS6502() self.address_parser = AddressParser() # self.assembler = Assembler(self.mpu, self.address_parser) m = ObservableMemory(addrWidth=self.mpu.ADDR_WIDTH) self.mpu.memory = m self.disassembler = Disassembler(self.mpu, self.address_parser) @property def a(self): return self.mpu.a @a.setter def a(self, value): self.mpu.a = value @property def x(self): return self.mpu.x @x.setter def x(self, value): self.mpu.x = value @property def y(self): return self.mpu.y @y.setter def y(self, value): self.mpu.y = value @property def pc(self): return self.mpu.pc @pc.setter def pc(self, value): self.mpu.pc = value @property def sp(self): return self.mpu.sp @sp.setter def sp(self, value): self.mpu.sp = value @property def p(self): return self.mpu.p @p.setter def p(self, value): self.mpu.p = value def get_memory(self, start, length): return self.mpu.memory[start:start+length] def set_memory(self, start, data): self.mpu.memory[start:start+len(data)] = data def step(self): self.mpu.step() def disassemble(self, pc): return self.disassembler.instruction_at(pc)