mirror of https://github.com/zeldaret/mm.git
				
				
				
			
		
			
				
	
	
		
			169 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
# SPDX-FileCopyrightText: © 2024 ZeldaRET
 | 
						|
# SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
import colorama
 | 
						|
colorama.init()
 | 
						|
 | 
						|
import argparse
 | 
						|
import sys
 | 
						|
import mapfile_parser
 | 
						|
from pathlib import Path
 | 
						|
 | 
						|
def eprint(*args, **kwargs):
 | 
						|
    print(*args, file=sys.stderr, **kwargs)
 | 
						|
 | 
						|
def mapPathToSource(origName: Path) -> Path:
 | 
						|
    # Try to map built path to the source path
 | 
						|
    parts = origName.parts
 | 
						|
    if parts[0] == "build":
 | 
						|
        parts = parts[2:]
 | 
						|
 | 
						|
    path = Path(*parts)
 | 
						|
    # Assume every file in the asm folder has .s extension, while everything else has .c extension
 | 
						|
    if path.parts[0] == "asm":
 | 
						|
        path = path.with_suffix(".s")
 | 
						|
    else:
 | 
						|
        path = path.with_suffix(".c")
 | 
						|
    return path
 | 
						|
 | 
						|
def compareMapFiles(mapFileBuild: Path, mapFileExpected: Path, section: str=".bss", reverseCheck: bool=True) -> mapfile_parser.MapsComparisonInfo:
 | 
						|
    eprint(f"Build mapfile:    {mapFileBuild}")
 | 
						|
    eprint(f"Expected mapfile: {mapFileExpected}")
 | 
						|
    eprint("")
 | 
						|
 | 
						|
    if not mapFileBuild.exists():
 | 
						|
        eprint(f"{colorama.Fore.LIGHTRED_EX}error{colorama.Fore.RESET}: mapfile not found at {mapFileBuild}. Did you forget to build the rom?")
 | 
						|
        exit(1)
 | 
						|
 | 
						|
    if not mapFileExpected.exists():
 | 
						|
        eprint(f"{colorama.Fore.LIGHTRED_EX}error{colorama.Fore.RESET}: expected mapfile not found at {mapFileExpected}. A mapfile under the 'expected' folder is required")
 | 
						|
        exit(1)
 | 
						|
 | 
						|
    buildMap = mapfile_parser.MapFile()
 | 
						|
    buildMap.readMapFile(mapFileBuild)
 | 
						|
    if section != "all":
 | 
						|
        buildMap = buildMap.filterBySectionType(section)
 | 
						|
 | 
						|
    expectedMap = mapfile_parser.MapFile()
 | 
						|
    expectedMap.readMapFile(mapFileExpected)
 | 
						|
    if section != "all":
 | 
						|
        expectedMap = expectedMap.filterBySectionType(section)
 | 
						|
 | 
						|
    return buildMap.compareFilesAndSymbols(expectedMap, checkOtherOnSelf=reverseCheck)
 | 
						|
 | 
						|
 | 
						|
def printSymbolComparison(comparisonInfo: mapfile_parser.MapsComparisonInfo, printAll = True):
 | 
						|
    print("Symbol Name,Build Address,Build File,Expected Address,Expected File,Difference,GOOD/BAD/MISSING")
 | 
						|
 | 
						|
    # If it's bad or missing, don't need to do anything special.
 | 
						|
    # If it's good, check for if it's in a file with bad or missing stuff, and check if print all is on. If none of these, print it.
 | 
						|
 | 
						|
    for symbolInfo in comparisonInfo.comparedList:
 | 
						|
        buildFile = symbolInfo.buildFile.filepath if symbolInfo.buildFile is not None else None
 | 
						|
        expectedFile = symbolInfo.expectedFile.filepath if symbolInfo.expectedFile is not None else None
 | 
						|
 | 
						|
        buildFileName = ""
 | 
						|
        if buildFile is not None:
 | 
						|
            buildFileName = mapPathToSource(buildFile)
 | 
						|
 | 
						|
        expectedFileName = ""
 | 
						|
        if expectedFile is not None:
 | 
						|
            expectedFileName = mapPathToSource(expectedFile)
 | 
						|
 | 
						|
        symbolGood = colorama.Fore.RED + "BAD" + colorama.Fore.RESET
 | 
						|
        if symbolInfo.diff is None:
 | 
						|
            symbolGood = colorama.Fore.YELLOW + "MISSING" + colorama.Fore.RESET
 | 
						|
            print(f"{symbolInfo.symbol.name},{symbolInfo.buildAddress:X},{buildFileName},{symbolInfo.expectedAddress:X},{expectedFileName},{symbolInfo.diff},{symbolGood}")
 | 
						|
            continue
 | 
						|
 | 
						|
        if symbolInfo.diff == 0:
 | 
						|
            symbolGood = colorama.Fore.GREEN + "GOOD" + colorama.Fore.RESET
 | 
						|
            if not buildFile in comparisonInfo.badFiles and not expectedFile in comparisonInfo.badFiles:
 | 
						|
                if not buildFile in comparisonInfo.badFiles and not expectedFile in comparisonInfo.badFiles:
 | 
						|
                    if not printAll:
 | 
						|
                        continue
 | 
						|
 | 
						|
        if buildFile != expectedFile:
 | 
						|
            symbolGood += colorama.Fore.CYAN + " MOVED" + colorama.Fore.RESET
 | 
						|
        print(f"{symbolInfo.symbol.name},{symbolInfo.buildAddress:X},{buildFileName},{symbolInfo.expectedAddress:X},{expectedFileName},{symbolInfo.diff:X},{symbolGood}")
 | 
						|
 | 
						|
 | 
						|
def printFileComparison(comparisonInfo: mapfile_parser.MapsComparisonInfo, fun_allowed: bool = True):
 | 
						|
    eprint("")
 | 
						|
 | 
						|
    if len(comparisonInfo.badFiles) != 0:
 | 
						|
        print(colorama.Fore.RED + "  BAD" + colorama.Style.RESET_ALL)
 | 
						|
 | 
						|
        for file in comparisonInfo.badFiles:
 | 
						|
            eprint(f"Symbol reordering in {mapPathToSource(file.filepath)}")
 | 
						|
        eprint("")
 | 
						|
 | 
						|
        if fun_allowed:
 | 
						|
            eprint(colorama.Fore.LIGHTWHITE_EX +
 | 
						|
            "  Symbols are REORDERED!!\n"
 | 
						|
            "  Oh! MY GOD!!" 
 | 
						|
            + colorama.Style.RESET_ALL)
 | 
						|
            eprint("")
 | 
						|
 | 
						|
    if len(comparisonInfo.missingFiles) != 0:
 | 
						|
        print(colorama.Fore.YELLOW + "  MISSING" + colorama.Style.RESET_ALL)
 | 
						|
 | 
						|
        for file in comparisonInfo.missingFiles:
 | 
						|
            eprint(f"Symbols missing from {mapPathToSource(file.filepath)}")
 | 
						|
        eprint("")
 | 
						|
 | 
						|
        if fun_allowed:
 | 
						|
            eprint(colorama.Fore.LIGHTWHITE_EX + "  Error, should (not) be in here " + colorama.Style.RESET_ALL)
 | 
						|
            eprint("")
 | 
						|
 | 
						|
        eprint("Some files appear to be missing symbols. Have they been renamed or declared as static? You may need to remake 'expected'")
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    description = "Check that globally visible symbols has not been reordered."
 | 
						|
    epilog = """\
 | 
						|
    N.B. Since this script reads the map files, it can only see globally visible symbols; in-function static symbols must be examined with other tools.
 | 
						|
    """
 | 
						|
 | 
						|
    parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
 | 
						|
    parser.add_argument("-v", "--version", help="MM version to check. Defaults to n64-us", default="n64-us")
 | 
						|
    parser.add_argument("-a", "--print-all", help="Print all symbols of the section, not just non-matching.", action="store_true")
 | 
						|
    parser.add_argument("-n", "--no-fun-allowed", help="Remove amusing messages.", action="store_true")
 | 
						|
    parser.add_argument("-r", "--no-reverse-check", help="Disable looking for symbols on the expected map that are missing on the built map file.", action="store_true")
 | 
						|
    parser.add_argument("-s", "--section", help="Specify which section should be checked for reordered symbols. Use `all` to check all sections. Defaults to .bss", default=".bss", choices=[".text", ".data", ".rodata", ".bss", "all"])
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    mapfilePath = Path(f"build/{args.version}/mm-{args.version}.map")
 | 
						|
    mapfileExpectedPath = "expected" / mapfilePath
 | 
						|
    reverseCheck: bool = not args.no_reverse_check
 | 
						|
    section: str = args.section
 | 
						|
 | 
						|
    comparisonInfo = compareMapFiles(mapfilePath, mapfileExpectedPath, section, reverseCheck)
 | 
						|
    printSymbolComparison(comparisonInfo, args.print_all)
 | 
						|
 | 
						|
    if len(comparisonInfo.badFiles) + len(comparisonInfo.missingFiles) != 0:
 | 
						|
        printFileComparison(comparisonInfo, not args.no_fun_allowed)
 | 
						|
        return 1
 | 
						|
 | 
						|
    eprint("")
 | 
						|
    eprint(colorama.Fore.GREEN + "  GOOD" + colorama.Style.RESET_ALL)
 | 
						|
 | 
						|
    if args.no_fun_allowed:
 | 
						|
        return 0
 | 
						|
 | 
						|
    eprint("\n" + colorama.Fore.LIGHTWHITE_EX +
 | 
						|
    colorama.Back.RED + f"                                  " + colorama.Back.RESET + "\n" +
 | 
						|
    colorama.Back.RED + f"         CONGRATURATIONS!         " + colorama.Back.RESET + "\n" +
 | 
						|
    colorama.Back.RED + "{:^34}".format(f"All Global {section if section != 'all' else 'Symbols'} is correct.") + colorama.Back.RESET + "\n" +
 | 
						|
    colorama.Back.RED + f"             THANK YOU!           " + colorama.Back.RESET + "\n" +
 | 
						|
    colorama.Back.RED + f"      You are great decomper!     " + colorama.Back.RESET + "\n" +
 | 
						|
    colorama.Back.RED + f"                                  " + colorama.Style.RESET_ALL )
 | 
						|
 | 
						|
    return 0
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    ret = main()
 | 
						|
    exit(ret)
 |