mirror of
https://github.com/michaelcmartin/Ophis.git
synced 2024-05-28 09:41:29 +00:00
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:
parent
e5ac21f0f9
commit
364b39edfb
|
@ -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
|
||||||
|
|
|
@ -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
80
src/Ophis/Listing.py
Normal 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
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user