First draft of listfile support.

The .listfile pragma and the -l command line option will select the
output file for the listing.
This commit is contained in:
Michael Martin 2013-03-24 18:26:48 -07:00
parent e5ac21f0f9
commit 364b39edfb
4 changed files with 199 additions and 21 deletions

View File

@ -20,6 +20,7 @@ print_labels = False
infiles = None infiles = None
outfile = None outfile = None
listfile = None
def parse_args(raw_args): def parse_args(raw_args):
@ -29,7 +30,7 @@ def parse_args(raw_args):
global warn_on_branch_extend global warn_on_branch_extend
global print_summary, print_loaded_files global print_summary, print_loaded_files
global print_pass, print_ir, print_labels global print_pass, print_ir, print_labels
global infiles, outfile global infiles, outfile, listfile
parser = optparse.OptionParser( parser = optparse.OptionParser(
usage="Usage: %prog [options] srcfile [srcfile ...]", usage="Usage: %prog [options] srcfile [srcfile ...]",
@ -37,6 +38,8 @@ def parse_args(raw_args):
parser.add_option("-o", default=None, dest="outfile", parser.add_option("-o", default=None, dest="outfile",
help="Output filename (default 'ophis.bin')") 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 = optparse.OptionGroup(parser, "Input options")
ingrp.add_option("-u", "--undoc", action="store_true", default=False, ingrp.add_option("-u", "--undoc", action="store_true", default=False,
@ -72,6 +75,7 @@ def parse_args(raw_args):
infiles = args infiles = args
outfile = options.outfile outfile = options.outfile
listfile = options.listfile
enable_branch_extend = options.enable_branch_extend enable_branch_extend = options.enable_branch_extend
enable_undoc_ops = options.undoc enable_undoc_ops = options.undoc
enable_65c02_exts = options.c02 enable_65c02_exts = options.c02

View File

@ -30,6 +30,14 @@ def pragmaOutfile(ppt, line, result):
Ophis.CmdLine.outfile = filename 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): def pragmaInclude(ppt, line, result):
"Includes a source file" "Includes a source file"
filename = line.expect("STRING").value filename = line.expect("STRING").value

80
src/Ophis/Listing.py Normal file
View File

@ -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

View File

@ -16,10 +16,9 @@ import Ophis.Errors as Err
import Ophis.IR as IR import Ophis.IR as IR
import Ophis.Opcodes as Ops import Ophis.Opcodes as Ops
import Ophis.CmdLine as Cmd import Ophis.CmdLine as Cmd
import Ophis.Listing as Listing
import Ophis.Macro as Macro import Ophis.Macro as Macro
# The passes themselves
class Pass(object): class Pass(object):
"""Superclass for all assembler passes. Automatically handles IR """Superclass for all assembler passes. Automatically handles IR
@ -670,20 +669,32 @@ class Assembler(Pass):
a file.""" a file."""
name = "Assembler" 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): def prePass(self):
self.output = [] self.output = []
self.code = 0 self.code = 0
self.data = 0 self.data = 0
self.filler = 0 self.filler = 0
if Cmd.listfile is not None:
self.listing = Listing.Listing(Cmd.listfile)
else:
self.listing = Listing.NullLister()
def postPass(self): def postPass(self):
self.listing.dump()
if Cmd.print_summary and Err.count == 0: if Cmd.print_summary and Err.count == 0:
print>>sys.stderr, "Assembly complete: %s bytes output " \ print>>sys.stderr, "Assembly complete: %s bytes output " \
"(%s code, %s data, %s filler)" \ "(%s code, %s data, %s filler)" \
% (len(self.output), % (len(self.output),
self.code, self.data, self.filler) self.code, self.data, self.filler)
def outputbyte(self, expr, env): def outputbyte(self, expr, env, tee=None):
'Outputs a byte, with range checking' 'Outputs a byte, with range checking'
if self.writeOK: if self.writeOK:
val = expr.value(env) val = expr.value(env)
@ -691,10 +702,12 @@ class Assembler(Pass):
Err.log("Byte constant " + str(expr) + " out of range") Err.log("Byte constant " + str(expr) + " out of range")
val = 0 val = 0
self.output.append(int(val)) self.output.append(int(val))
if tee is not None:
tee.append(self.output[-1])
else: else:
Err.log("Attempt to write to data segment") 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' 'Outputs a little-endian word, with range checking'
if self.writeOK: if self.writeOK:
val = expr.value(env) val = expr.value(env)
@ -703,10 +716,12 @@ class Assembler(Pass):
val = 0 val = 0
self.output.append(int(val & 0xFF)) self.output.append(int(val & 0xFF))
self.output.append(int((val >> 8) & 0xFF)) self.output.append(int((val >> 8) & 0xFF))
if tee is not None:
tee.extend(self.output[-2:])
else: else:
Err.log("Attempt to write to data segment") 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' 'Outputs a little-endian dword, with range checking'
if self.writeOK: if self.writeOK:
val = expr.value(env) val = expr.value(env)
@ -717,10 +732,12 @@ class Assembler(Pass):
self.output.append(int((val >> 8) & 0xFF)) self.output.append(int((val >> 8) & 0xFF))
self.output.append(int((val >> 16) & 0xFF)) self.output.append(int((val >> 16) & 0xFF))
self.output.append(int((val >> 24) & 0xFF)) self.output.append(int((val >> 24) & 0xFF))
if tee is not None:
tee.extend(self.output[-4:])
else: else:
Err.log("Attempt to write to data segment") 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' 'Outputs a big-endian word, with range checking'
if self.writeOK: if self.writeOK:
val = expr.value(env) val = expr.value(env)
@ -729,10 +746,12 @@ class Assembler(Pass):
val = 0 val = 0
self.output.append(int((val >> 8) & 0xFF)) self.output.append(int((val >> 8) & 0xFF))
self.output.append(int(val & 0xFF)) self.output.append(int(val & 0xFF))
if tee is not None:
tee.extend(self.output[-2:])
else: else:
Err.log("Attempt to write to data segment") 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' 'Outputs a big-endian dword, with range checking'
if self.writeOK: if self.writeOK:
val = expr.value(env) val = expr.value(env)
@ -743,6 +762,8 @@ class Assembler(Pass):
self.output.append(int((val >> 16) & 0xFF)) self.output.append(int((val >> 16) & 0xFF))
self.output.append(int((val >> 8) & 0xFF)) self.output.append(int((val >> 8) & 0xFF))
self.output.append(int(val & 0xFF)) self.output.append(int(val & 0xFF))
if tee is not None:
tee.extend(self.output[-4:])
else: else:
Err.log("Attempt to write to data segment") Err.log("Attempt to write to data segment")
@ -757,6 +778,40 @@ class Assembler(Pass):
arg += 256 arg += 256
return IR.ConstantExpr(arg) 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): def assemble(self, node, mode, env):
"A generic instruction called by the visitor methods themselves" "A generic instruction called by the visitor methods themselves"
(opcode, expr, expr2) = node.data (opcode, expr, expr2) = node.data
@ -765,19 +820,30 @@ class Assembler(Pass):
Err.log('%s does not have mode "%s"' % (opcode.upper(), Err.log('%s does not have mode "%s"' % (opcode.upper(),
Ops.modes[mode])) Ops.modes[mode]))
return return
self.outputbyte(IR.ConstantExpr(bin_op), env) inst_bytes = []
self.outputbyte(IR.ConstantExpr(bin_op), env, inst_bytes)
arglen = Ops.lengths[mode] 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 if mode == 15: # ZP Relative mode is wildly nonstandard
expr2 = self.relativize(expr2, env, arglen) expr2 = self.relativize(expr2, env, arglen)
self.outputbyte(expr, env) self.outputbyte(expr, env, inst_bytes)
self.outputbyte(expr2, env) self.outputbyte(expr2, env, inst_bytes)
else: else:
if mode == 14: if mode == 14:
expr = self.relativize(expr, env, arglen) expr = self.relativize(expr, env, arglen)
if arglen == 1: if arglen == 1:
self.outputbyte(expr, env) self.outputbyte(expr, env, inst_bytes)
elif arglen == 2: 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) env.incPC(1 + arglen)
self.code += 1 + arglen self.code += 1 + arglen
@ -833,8 +899,10 @@ class Assembler(Pass):
pass pass
def visitByte(self, node, env): def visitByte(self, node, env):
created = []
for expr in node.data: for expr in node.data:
self.outputbyte(expr, env) self.outputbyte(expr, env, created)
self.registerData(created, env.getPC())
env.incPC(len(node.data)) env.incPC(len(node.data))
self.data += len(node.data) self.data += len(node.data)
@ -850,37 +918,50 @@ class Assembler(Pass):
elif offset + length > len(node.data): elif offset + length > len(node.data):
Err.log("File too small for .incbin subrange") Err.log("File too small for .incbin subrange")
else: else:
created = []
for expr in node.data[offset:(offset + length)]: 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) env.incPC(length)
self.data += length self.data += length
def visitWord(self, node, env): def visitWord(self, node, env):
created = []
for expr in node.data: 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) env.incPC(len(node.data) * 2)
self.data += len(node.data) * 2 self.data += len(node.data) * 2
def visitDword(self, node, env): def visitDword(self, node, env):
created = []
for expr in node.data: 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) env.incPC(len(node.data) * 4)
self.data += len(node.data) * 4 self.data += len(node.data) * 4
def visitWordBE(self, node, env): def visitWordBE(self, node, env):
created = []
for expr in node.data: 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) env.incPC(len(node.data) * 2)
self.data += len(node.data) * 2 self.data += len(node.data) * 2
def visitDwordBE(self, node, env): def visitDwordBE(self, node, env):
created = []
for expr in node.data: 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) env.incPC(len(node.data) * 4)
self.data += len(node.data) * 4 self.data += len(node.data) * 4
def visitSetPC(self, node, env): 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): def visitCheckPC(self, node, env):
pc = env.getPC() pc = env.getPC()
@ -895,7 +976,12 @@ class Assembler(Pass):
Err.log("Attempted to .advance backwards: $%x to $%x" % Err.log("Attempted to .advance backwards: $%x to $%x" %
(pc, target)) (pc, target))
else: else:
created = []
for i in xrange(target - pc): for i in xrange(target - pc):
self.outputbyte(node.data[1], env) self.outputbyte(node.data[1], env, created)
self.filler += target - pc self.filler += target - pc
self.registerData(created, env.getPC())
env.setPC(target) env.setPC(target)
def registerData(self, vals, pc):
self.listing.listData(vals, pc)