https://github.com/virtualagc/virtualagc
Revision 078c79d8734a9ed2860303a7c1662004284fe853 authored by Ron Burkey on 07 August 2022, 15:04:04 UTC, committed by Ron Burkey on 07 August 2022, 15:04:04 UTC
assembly listings from yaASM and yaLEMAP. Added some debugging messages
to 'make install'.  Tweaked debugging messages that VirtualAGC embeds in
'simulate'.  Verified buildability in Mint 21, 20, 19, 17, and verified
buildability using clang in Mint 17.
1 parent 6bb1acc
Raw File
Tip revision: 078c79d8734a9ed2860303a7c1662004284fe853 authored by Ron Burkey on 07 August 2022, 15:04:04 UTC
Fixed a potential string-overflow bug in yaASM. Removed timestamps from
Tip revision: 078c79d
ropediff.py
#!/usr/bin/env python

# Copyright 2010 Jim lawton <jim dot lawton at gmail dot com>
#
# This file is part of yaAGC.
#
# yaAGC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# yaAGC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with yaAGC; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Python script to find the differences between two supplied AGC rope binaries.

import os
import sys
import glob
from optparse import OptionParser
import struct
import operator
import listing_analyser


class CoreDiff:
    """Class defining information about a difference between 2 core files."""

    def __init__(self, coreaddr, address, leftval, rightval):
        self.coreaddr = coreaddr    # Starting address in the core file.
        self.address = address      # Starting address in the listing.
        self.leftval = leftval      # Left value.
        self.rightval = rightval    # Right value.
        self.pagenum = None
        self.module = None
        self.srcline = None
        self.linenum = None

    def setloc(self, pagenum, module, linenum, srcline):
        self.pagenum = pagenum      # Listing page number.
        self.module = module        # Source module.
        self.linenum = linenum      # Listing line number.
        self.srcline = srcline      # Source line.
        self.srcline = srcline[:100]
        if self.srcline.endswith('\n'):
            self.srcline = self.srcline[:-1]

    def __str__(self):
        line = "%06o (%7s) %05o %05o " % (self.coreaddr, self.address, self.leftval, self.rightval)
        if self.pagenum:
            line += "%4d " % (self.pagenum)
        else:
            line += "     "
        line += "%-48s " % (self.module)
        srcline = self.srcline
        if self.srcline:
            srcline = self.srcline.rstrip()
        line += "%s" % (srcline)
        return line

    def __cmp__(self, other):
        return (self.coreaddr - other.coreaddr)

def log(text, verbose=False, newline=True):
    if verbose == False or (verbose == True and options.verbose == True):
        if options.outfile:
            print >>options.outfile, text,
            if newline:
                print >>options.outfile
        if verbose == True:
            print text,
            if newline:
                print

def main():

    CORELEN2 = (2 * 044 * 02000)    # Block II
    CORELEN1 = (2 * 034 * 02000)    # Block I

    global options

    parser = OptionParser("usage: %prog [options] core1 core2")
    parser.add_option("-p", "--by-page",      action="store_true",  dest="bypage",      default=False,                 help="Sort differences by page number.")
    parser.add_option("-c", "--no-checksums", action="store_false", dest="checksums",   default=True,                  help="Discard differences in checksums.")
    parser.add_option("-N", "--no-super",     action="store_true",  dest="noSuper",     default=False,                 help="Discard differences in which one word has 100 in bits 5,6,7 and the other has 011.")
    parser.add_option("-S", "--only-super",   action="store_true",  dest="onlySuper",   default=False,                 help="Show only differences involving 100 vs. 011 in bits 5,6,7.")
    parser.add_option("-Z", "--no-zero",      action="store_true",  dest="noZero",      default=False,                 help="Discard differences in which the word from the 2nd file is 00000.")
    parser.add_option("-s", "--stats",        action="store_true",  dest="stats",       default=False,                 help="Print statistics.")
    parser.add_option("-v", "--verbose",      action="store_true",  dest="verbose",     default=False,                 help="Print extra information.")
    parser.add_option("-o", "--output",                             dest="outfilename",                metavar="FILE", help="Write output to file.")
    parser.add_option("-a", "--annotate",     action="store_true",  dest="annotate",    default=False,                 help="Output a modified listing annotated with core differences.")

    (options, args) = parser.parse_args()

    options.analyse = True

    options.outfile = None
    if options.outfilename:
        options.outfile = open(options.outfilename, "w")
    else:
        options.outfile = sys.stdout

    if len(args) < 2:
        parser.error("Two core files must be supplied!")
        sys.exit(1)

    cores = []
    for arg in args:
        cores.append(arg)
        if not os.path.isfile(arg):
            parser.error("File \"%s\" does not exist" % arg)
            sys.exit(1)

    sizes = []
    for core in cores:
        sizes.append(os.path.getsize(core))

    if sizes[0] != sizes[1]:
        parser.error("Core files are not the same size!")
        sys.exit(1)

    if sizes[0] != CORELEN2 and sizes[0] != CORELEN1:
        parser.error("Core files are incorrect length, must be %d (Block II) or %d (Block I) bytes!" % (CORELEN2, CORELEN1))
        sys.exit(1)

    log("yaAGC Core Rope Differencer")
    log("")
    log("Left core file:  %s" % cores[0])
    log("Right core file: %s" % cores[1])

    leftcore = open(cores[0], "rb")
    rightcore = open(cores[1], "rb")

    leftdir = os.path.abspath(os.path.dirname(cores[0]))
    leftlst = os.path.join(leftdir, "*.lst")
    rightdir = os.path.abspath(os.path.dirname(cores[1]))
    rightlst = os.path.join(rightdir, "*.lst")
    lfiles = glob.glob(leftlst)
    lfiles.extend(glob.glob(rightlst))
    # Remove duplicates.
    ldict = {}
    for x in lfiles:
        ldict[x] = x
    lfiles = ldict.values()

    if len(lfiles) == 0:
        print >>sys.stderr, "Warning: no listing file for analysis!"
        options.analyse = False

    listfile = None
    if options.analyse:
        if len(lfiles) > 1:
            for l in lfiles:
                if l.endswith("MAIN.lst"):
                    lfiles.remove(l)
            if len(lfiles) > 1:
                print >>sys.stderr, "Warning: multiple listing files, using %s!" % (lfiles[0])
        listfile = lfiles[0]
        if not os.path.isfile(listfile):
            parser.error("File \"%s\" does not exist" % listfile)
            sys.exit(1)
        log("")
        log("Listing: %s" % listfile)
        log("Build: %s" % os.path.basename(os.path.dirname(listfile).split('.')[0]))

        log("Analysing listing file... ", verbose=True)
        blocks = listing_analyser.analyse(listfile)

    options.annofile = None
    if options.annotate:
        if listfile == None:
            sys.exit("Annotate option specified, but no input listing file found")
        afilename = listfile
        afilename = afilename.replace(".lst", ".anno.txt")
        options.annofile = open(afilename, 'w')

    diffcount = {}
    difftotal = 0

    modlist = []
    srcfiles = glob.glob(os.path.join(leftdir, "*.agc"))
    srcfiles.remove(os.path.join(leftdir, "MAIN.agc"))
    if "Templates.agc" in srcfiles:
        srcfiles.remove(os.path.join(leftdir, "Template.agc"))
    for srcfile in srcfiles:
        modlist.append(os.path.basename(srcfile).split('.')[0])
    for module in modlist:
        diffcount[module] = 0

    log("Reading MAIN.agc... ", verbose=True)
    includelist = []
    mainfile = open(os.path.join(leftdir, "MAIN.agc"), "r")
    mainlines = mainfile.readlines()
    for line in mainlines:
        if line.startswith('$'):
            module = line.split()[0].split('.')[0][1:]
            includelist.append(module)
    mainfile.close()

    diffs = []
    lines = []

    log("Comparing core image files... ", verbose=True)
    try:
        while True:
            leftdata = leftcore.read(2)
            rightdata = rightcore.read(2)
            if not leftdata or not rightdata:
                break
            # Read 16-bit word and unpack into 2 byte tuple, native endianness.
            leftword = struct.unpack("BB", leftdata)
            rightword = struct.unpack("BB", rightdata)
            if leftword[0] != rightword[0] or leftword[1] != rightword[1]:
                # Words differ. Check super bits.
                leftval = (leftword[0] << 7) | (leftword[1] >> 1)
                rightval = (rightword[0] << 7) | (rightword[1] >> 1)
                if options.noZero and rightval == 0:
                    continue
                if ((leftval ^ rightval) & 0160) == 0160 and ((leftval & 0160) == 0100 or (leftval & 0160) == 0060):
                    if options.noSuper:
                        continue
                else:
                    if options.onlySuper:
                        continue
                i = (leftcore.tell() - 2) / 2
                offset = 02000 + (i % 02000)
                bank = i / 02000
                if bank < 4:
                    bank ^= 2
                line = "%06o (" % i
                if i < 04000:
                    address = "   %04o" % (i + 04000)
                else:
                    address = "%02o,%04o" % (bank, offset)
                line += "%s)   " % address
                line += "%05o   %05o" % (leftval, rightval)
                if options.analyse:
                    block = listing_analyser.findBlock(blocks, i)
                    if block:
                        line += "   " + block.getInfo()
                        diffcount[block.module] += 1
                diffs.append(CoreDiff(i, address, leftval, rightval))
                difftotal += 1
                lines.append(line)
    finally:
        leftcore.close()
        rightcore.close()

    log("%d core image differences" % (difftotal), verbose=True)

    lines = {}
    buggers = []
    module = None
    pagenum = 0
    address = 0
    checkdiffs = 0
    linenum = 0

    log("Building module/page/line list... ", verbose=True)
    for line in open(listfile, "r"):
        linenum += 1
        elems = line.split()
        if len(elems) > 0:
            if not line.startswith(' '):
                if "# Page " in line and "scans" not in line:
                    pagenum = line.split()[3]
                    if pagenum.isdigit():
                        pagenum = int(pagenum)
                if elems[0][0].isdigit():
                    if len(elems) > 1:
                        if elems[1].startswith('$'):
                            module = elems[1][1:].split('.')[0]
                        else:
                            if len(elems) > 2:
                                if elems[1][0].isdigit() and elems[2][0].isdigit() and len(elems[2]) == 5:
                                    address = elems[1]
                                    lines[address] = (module, pagenum, linenum, line)
                                    if len(elems) > 3:
                                        # Handle 2-word quantities, yaYUL outputs listing for the two combined at the address of the first.
                                        if elems[3][0].isdigit() and len(elems[3]) == 5:
                                            if "," in address:
                                                bank = int(address.split(',')[0], 8)
                                                offset = int(address.split(',')[1], 8)
                                                offset += 1
                                                address = "%02o,%04o" % (bank, offset)
                                            else:
                                                offset = int(address, 8)
                                                offset += 1
                                                address = "%04o" % offset
                                            lines[address] = (module, pagenum, linenum, line)
                if line.startswith("Bugger"):
                    buggers.append(line)

    log("Setting diff locations... ", verbose=True)
    for diff in diffs:
        address = diff.address.strip()
        if address in lines.keys():
            (module, pagenum, linenum, line) = lines[address]
            diff.setloc(pagenum, module, linenum, line)
        elif diff.srcline == None:
            foundBugger = False
            for bugger in buggers:
                bval = bugger.split()[2]
                baddr = bugger.split()[4]
                if baddr.endswith('.'):
                    baddr = baddr[:-1]
                if address == baddr:
                    diff.setloc(0, "Checksum", 0, "%s%s%s%s" % (15 * ' ', baddr, 11 * ' ', bval))
                    checkdiffs += 1
                    foundBugger = True
                    break
            if not foundBugger:
                print >>sys.stderr, "Error: address %s not found in listing file" % (address)
                log("Error: address %s not found in listing file" % (address))
        else:
            print >>sys.stderr, "Error: address %s not found in listing file" % (address)
            log("Error: address %s not found in listing file" % (address))

    log("")
    log("%s" % ("Total core differences: %d (checksums=%d)" % (difftotal, checkdiffs)))

    # Sort by page/line.
    if options.bypage == True:
        newdiffs = []
        diffIndex = {}
        diffIndex[0] = []
        for diff in diffs:
            if diff.pagenum is None or diff.pagenum == 0:
                diffIndex[0].append(diff)
                continue
            if diff.pagenum not in diffIndex.keys():
                diffIndex[diff.pagenum] = {}
            diffIndex[diff.pagenum][diff.linenum] = diff
        pages = diffIndex.keys()
        pages.sort()
        for diff in diffIndex[0]:
            newdiffs.append(diff)
        for page in pages[1:]:
            lines = diffIndex[page].keys()
            lines.sort()
            for line in lines:
                newdiffs.append(diffIndex[page][line])
        diffs = newdiffs
        log("%s" % ("Source difference lines: %d" % len(diffs)))

    log("")

    if difftotal > 0:
        log("Core address     Left  Right Page Module                                           Line Number    Address           Source")
        log("---------------- ----- ----- ---- ------------------------------------------------ -------------- -------           ------------------------------------------------")
        log("")
        for diff in diffs:
            if options.checksums == True or (options.checksums == False and diff.module != "Checksum"):
                log(diff.__str__())

        if options.annofile:
            linenums = []
            diffsbyline = {}
            for diff in diffs:
                if diff.linenum != None and diff.linenum != 0:
                    linenums.append(diff.linenum)
                    diffsbyline[diff.linenum] = diff
            linenums.sort()
            diffindex = 0
            linenum = 0
            for line in open(listfile, "r"):
                linenum += 1
                if diffindex < len(linenums) and linenum == linenums[diffindex]:
                    diff = diffsbyline[linenum]
                    print >>options.annofile
                    print >>options.annofile, ">>> Core error %d of %d at %s: expected %05o, got %05o" % (diffindex + 1, len(linenums), diff.address, diff.leftval, diff.rightval)
                    diffindex += 1
                print >>options.annofile, line,

        if options.stats:

            diffblocks = []
            index = 0
            while index < len(diffs) - 1:
                cur = index
                end = index + 1
                while diffs[end].coreaddr == diffs[cur].coreaddr + 1:
                    cur += 1
                    end += 1
                length = end - index - 1
                if length > 1:
                    diffblocks.append((diffs[index], length))
                index = end

            diffblocks.sort()

            if len(diffblocks) > 0:
                log("")
                log("")
                log("Difference blocks: (sorted by length, ignoring single isolated differences)")
                #log("-" * 80)
                log("")
                log("Core address        Diffs   Module                                               ")
                log("----------------    -----   ---------------------------------------------------- ")

                for (diff, length) in sorted(diffblocks, key=operator.itemgetter(1), reverse=True):
                    address = diff.address
                    if "," in address:
                        bank = int(address.split(',')[0], 8)
                        offset = int(address.split(',')[1], 8)
                        i = 010000 + bank * 02000 + offset
                    else:
                        i = int(address, 8)
                    line = "%06o (" % i
                    offset = 02000 + (i % 02000)
                    bank = i / 02000
                    if bank < 4:
                        bank ^= 2
                    if i < 04000:
                        line += "   %04o)   " % (i + 04000)
                    else:
                        line += "%02o,%04o)   " % (bank, offset)
                    line += "%6d" % length
                    block = listing_analyser.findBlock(blocks, i)
                    if block:
                        line += "   " + block.getInfo()
                    log(line)
                log("-" * 80)

            counts = []
            for module in diffcount:
                counts.append((module, diffcount[module]))
            counts.sort()

            if options.stats:
                log("")
                log("Per-module differences: (sorted by errors)")
                log("-" * 80)
                for count in sorted(counts, key=operator.itemgetter(1), reverse=True):
                    log("%-48s %6d" % count)
                log("-" * 80)

                log("")
                log("Per-module differences: (sorted by module)")
                log("-" * 80)
                for count in counts:
                    log("%-48s %6d" % count)
                log("-" * 80)

                log("")
                log("Per-module differences: (sorted by include order)")
                log("-" * 80)
                for module in includelist:
                    log("%-48s %6d" % (module, diffcount[module]))
                log("-" * 80)

    log("Done", verbose=True)

    if options.annofile:
        options.annofile.close()

    if options.outfile:
        options.outfile.close()

    if difftotal > 0:
        if options.outfilename:
            print "Core differences are in", options.outfilename
        else:
            print "Core differences found"
    else:
        print "No core differences found"

if __name__=="__main__":
    sys.exit(main())
back to top