Revision 66d8e606a8d996ded60bc81d5edf319142a5fad9 authored by Ron Burkey on 04 October 2021, 11:49:55 UTC, committed by Ron Burkey on 04 October 2021, 11:49:55 UTC
2 parent s dfc2190 + 42c2282
Raw File
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