From 364b39edfbed2bc35a6a06826268644e978bd06c Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 24 Mar 2013 18:26:48 -0700 Subject: [PATCH] First draft of listfile support. The .listfile pragma and the -l command line option will select the output file for the listing. --- src/Ophis/CmdLine.py | 6 +- src/Ophis/CorePragmas.py | 8 +++ src/Ophis/Listing.py | 80 +++++++++++++++++++++++++ src/Ophis/Passes.py | 126 ++++++++++++++++++++++++++++++++------- 4 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 src/Ophis/Listing.py diff --git a/src/Ophis/CmdLine.py b/src/Ophis/CmdLine.py index 3ceadf6..562bdbe 100644 --- a/src/Ophis/CmdLine.py +++ b/src/Ophis/CmdLine.py @@ -20,6 +20,7 @@ print_labels = False infiles = None outfile = None +listfile = None def parse_args(raw_args): @@ -29,7 +30,7 @@ def parse_args(raw_args): global warn_on_branch_extend global print_summary, print_loaded_files global print_pass, print_ir, print_labels - global infiles, outfile + global infiles, outfile, listfile parser = optparse.OptionParser( usage="Usage: %prog [options] srcfile [srcfile ...]", @@ -37,6 +38,8 @@ def parse_args(raw_args): parser.add_option("-o", default=None, dest="outfile", help="Output filename (default 'ophis.bin')") + parser.add_option("-l", default=None, dest="listfile", + help="Listing filename (not created by default)") ingrp = optparse.OptionGroup(parser, "Input options") ingrp.add_option("-u", "--undoc", action="store_true", default=False, @@ -72,6 +75,7 @@ def parse_args(raw_args): infiles = args outfile = options.outfile + listfile = options.listfile enable_branch_extend = options.enable_branch_extend enable_undoc_ops = options.undoc enable_65c02_exts = options.c02 diff --git a/src/Ophis/CorePragmas.py b/src/Ophis/CorePragmas.py index 3f1506d..a1ac27e 100644 --- a/src/Ophis/CorePragmas.py +++ b/src/Ophis/CorePragmas.py @@ -30,6 +30,14 @@ def pragmaOutfile(ppt, line, result): Ophis.CmdLine.outfile = filename +def pragmaListfile(ppt, line, result): + "Sets the listing file if it hasn't already been set" + filename = line.expect("STRING").value + line.expect("EOL") + if type(filename) == str and Ophis.CmdLine.listfile is None: + Ophis.CmdLine.listfile = filename + + def pragmaInclude(ppt, line, result): "Includes a source file" filename = line.expect("STRING").value diff --git a/src/Ophis/Listing.py b/src/Ophis/Listing.py new file mode 100644 index 0000000..f150007 --- /dev/null +++ b/src/Ophis/Listing.py @@ -0,0 +1,80 @@ +"""The Ophis program lister + + When displaying an assembled binary for human inspection, it is + traditional to mix binary with reconstructed instructions that + have all arguments precomputed. This class manages that.""" + +# Copyright 2002-2012 Michael C. Martin and additional contributors. +# You may use, modify, and distribute this file under the MIT +# license: See README for details. + +import sys + + +class Listing(object): + """Encapsulates the program listing. Accepts fully formatted + instruction strings, or batches of data bytes. Batches of data + bytes are assumed to be contiguous unless a divider is explicitly + requested.""" + + def __init__(self, fname): + self.listing = [(0, [])] + self.filename = fname + + def listInstruction(self, inst): + "Add a preformatted instruction list to the listing." + self.listing.append(inst) + + def listDivider(self, newpc): + "Indicate that the next data block will begin at the given PC." + self.listing.append((newpc, [])) + + def listData(self, vals, pc): + """Add a batch of data to the listing. If this starts a new + batch of data, begin that batch at the listed PC.""" + if type(self.listing[-1]) is not tuple: + self.listing.append((pc, [])) + self.listing[-1][1].extend(vals) + + def dump(self): + if self.filename == "-": + out = sys.stdout + else: + out = file(self.filename, "w") + for x in self.listing: + if type(x) is str: + print>>out, x + elif type(x) is tuple: + i = 0 + pc = x[0] + while True: + row = x[1][i:i + 16] + if row == []: + break + dataline = " %04X " % (pc + i) + dataline += (" %02X" * len(row)) % tuple(row) + charline = "" + for c in row: + if c < 31 or c > 127: + charline += "." + else: + charline += chr(c) + print>>out, "%-54s |%-16s|" % (dataline, charline) + i += 16 + if self.filename != "-": + out.close() + + +class NullLister(object): + "A dummy Lister that actually does nothing." + def listInstruction(self, inst): + pass + + def listDivider(self, newpc): + pass + + def listData(self, vals, pc): + pass + + def dump(self): + pass diff --git a/src/Ophis/Passes.py b/src/Ophis/Passes.py index 25eaec6..2712066 100644 --- a/src/Ophis/Passes.py +++ b/src/Ophis/Passes.py @@ -16,10 +16,9 @@ import Ophis.Errors as Err import Ophis.IR as IR import Ophis.Opcodes as Ops import Ophis.CmdLine as Cmd +import Ophis.Listing as Listing import Ophis.Macro as Macro -# The passes themselves - class Pass(object): """Superclass for all assembler passes. Automatically handles IR @@ -670,20 +669,32 @@ class Assembler(Pass): a file.""" name = "Assembler" + # The self.listing field defined in prePass and referred to elsewhere + # holds the necessary information to build the program listing later. + # Each member of this list is either a string (in which case it is + # a listed instruction to be echoed) or it is a tuple of + # (startPC, bytes). Byte listings need to be interrupted when the + # PC is changed by an .org, which may happen in the middle of + # data definition blocks. def prePass(self): self.output = [] self.code = 0 self.data = 0 self.filler = 0 + if Cmd.listfile is not None: + self.listing = Listing.Listing(Cmd.listfile) + else: + self.listing = Listing.NullLister() def postPass(self): + self.listing.dump() if Cmd.print_summary and Err.count == 0: print>>sys.stderr, "Assembly complete: %s bytes output " \ "(%s code, %s data, %s filler)" \ % (len(self.output), self.code, self.data, self.filler) - def outputbyte(self, expr, env): + def outputbyte(self, expr, env, tee=None): 'Outputs a byte, with range checking' if self.writeOK: val = expr.value(env) @@ -691,10 +702,12 @@ class Assembler(Pass): Err.log("Byte constant " + str(expr) + " out of range") val = 0 self.output.append(int(val)) + if tee is not None: + tee.append(self.output[-1]) else: Err.log("Attempt to write to data segment") - def outputword(self, expr, env): + def outputword(self, expr, env, tee=None): 'Outputs a little-endian word, with range checking' if self.writeOK: val = expr.value(env) @@ -703,10 +716,12 @@ class Assembler(Pass): val = 0 self.output.append(int(val & 0xFF)) self.output.append(int((val >> 8) & 0xFF)) + if tee is not None: + tee.extend(self.output[-2:]) else: Err.log("Attempt to write to data segment") - def outputdword(self, expr, env): + def outputdword(self, expr, env, tee=None): 'Outputs a little-endian dword, with range checking' if self.writeOK: val = expr.value(env) @@ -717,10 +732,12 @@ class Assembler(Pass): self.output.append(int((val >> 8) & 0xFF)) self.output.append(int((val >> 16) & 0xFF)) self.output.append(int((val >> 24) & 0xFF)) + if tee is not None: + tee.extend(self.output[-4:]) else: Err.log("Attempt to write to data segment") - def outputword_be(self, expr, env): + def outputword_be(self, expr, env, tee=None): 'Outputs a big-endian word, with range checking' if self.writeOK: val = expr.value(env) @@ -729,10 +746,12 @@ class Assembler(Pass): val = 0 self.output.append(int((val >> 8) & 0xFF)) self.output.append(int(val & 0xFF)) + if tee is not None: + tee.extend(self.output[-2:]) else: Err.log("Attempt to write to data segment") - def outputdword_be(self, expr, env): + def outputdword_be(self, expr, env, tee=None): 'Outputs a big-endian dword, with range checking' if self.writeOK: val = expr.value(env) @@ -743,6 +762,8 @@ class Assembler(Pass): self.output.append(int((val >> 16) & 0xFF)) self.output.append(int((val >> 8) & 0xFF)) self.output.append(int(val & 0xFF)) + if tee is not None: + tee.extend(self.output[-4:]) else: Err.log("Attempt to write to data segment") @@ -757,6 +778,40 @@ class Assembler(Pass): arg += 256 return IR.ConstantExpr(arg) + def listing_string(self, pc, binary, mode, opcode, val1, val2): + base = " %04X " % pc + base += (" %02X" * len(binary)) % tuple(binary) + formats = ["", + "#$%02X", + "$%02X", + "$%02X, X", + "$%02X, Y", + "$%04X", + "$%04X, X", + "$%04X, Y", + "($%04X)", + "($%04X, X)", + "($%04X), Y", + "($%02X)", + "($%02X, X)", + "($%02X), Y", + "$%04X", + "$%02X, $%04X"] + fmt = ("%-16s %-5s" % (base, opcode.upper())) + formats[mode] + if val1 is None: + return fmt + elif val2 is None: + arglen = Ops.lengths[mode] + mask = 0xFF + # Relative is a full address in a byte, so it also has the + # 0xFFFF mask. + if arglen == 2 or mode == 14: + mask = 0xFFFF + return fmt % (val1 & mask) + else: + # Mode is 15: Zero Page, Relative + return fmt % (val1 & 0xFF, val2 & 0xFFFF) + def assemble(self, node, mode, env): "A generic instruction called by the visitor methods themselves" (opcode, expr, expr2) = node.data @@ -765,19 +820,30 @@ class Assembler(Pass): Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) return - self.outputbyte(IR.ConstantExpr(bin_op), env) + inst_bytes = [] + self.outputbyte(IR.ConstantExpr(bin_op), env, inst_bytes) arglen = Ops.lengths[mode] + val1 = None + val2 = None + if expr is not None: + val1 = expr.value(env) + if expr2 is not None: + val2 = expr2.value(env) if mode == 15: # ZP Relative mode is wildly nonstandard expr2 = self.relativize(expr2, env, arglen) - self.outputbyte(expr, env) - self.outputbyte(expr2, env) + self.outputbyte(expr, env, inst_bytes) + self.outputbyte(expr2, env, inst_bytes) else: if mode == 14: expr = self.relativize(expr, env, arglen) if arglen == 1: - self.outputbyte(expr, env) + self.outputbyte(expr, env, inst_bytes) elif arglen == 2: - self.outputword(expr, env) + self.outputword(expr, env, inst_bytes) + self.listing.listInstruction(self.listing_string(env.getPC(), + inst_bytes, + mode, opcode, + val1, val2)) env.incPC(1 + arglen) self.code += 1 + arglen @@ -833,8 +899,10 @@ class Assembler(Pass): pass def visitByte(self, node, env): + created = [] for expr in node.data: - self.outputbyte(expr, env) + self.outputbyte(expr, env, created) + self.registerData(created, env.getPC()) env.incPC(len(node.data)) self.data += len(node.data) @@ -850,37 +918,50 @@ class Assembler(Pass): elif offset + length > len(node.data): Err.log("File too small for .incbin subrange") else: + created = [] for expr in node.data[offset:(offset + length)]: - self.outputbyte(expr, env) + self.outputbyte(expr, env, created) + self.registerData(created, env.getPC()) env.incPC(length) self.data += length def visitWord(self, node, env): + created = [] for expr in node.data: - self.outputword(expr, env) + self.outputword(expr, env, created) + self.registerData(created, env.getPC()) env.incPC(len(node.data) * 2) self.data += len(node.data) * 2 def visitDword(self, node, env): + created = [] for expr in node.data: - self.outputdword(expr, env) + self.outputdword(expr, env, created) + self.registerData(created, env.getPC()) env.incPC(len(node.data) * 4) self.data += len(node.data) * 4 def visitWordBE(self, node, env): + created = [] for expr in node.data: - self.outputword_be(expr, env) + self.outputword_be(expr, env, created) + self.registerData(created, env.getPC()) env.incPC(len(node.data) * 2) self.data += len(node.data) * 2 def visitDwordBE(self, node, env): + created = [] for expr in node.data: - self.outputdword_be(expr, env) + self.outputdword_be(expr, env, created) + self.registerData(created, env.getPC()) env.incPC(len(node.data) * 4) self.data += len(node.data) * 4 def visitSetPC(self, node, env): - env.setPC(node.data[0].value(env)) + val = node.data[0].value(env) + if self.writeOK and env.getPC() != val: + self.listing.listDivider(val) + env.setPC(val) def visitCheckPC(self, node, env): pc = env.getPC() @@ -895,7 +976,12 @@ class Assembler(Pass): Err.log("Attempted to .advance backwards: $%x to $%x" % (pc, target)) else: + created = [] for i in xrange(target - pc): - self.outputbyte(node.data[1], env) + self.outputbyte(node.data[1], env, created) self.filler += target - pc + self.registerData(created, env.getPC()) env.setPC(target) + + def registerData(self, vals, pc): + self.listing.listData(vals, pc)