1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
#!/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)
|