summaryrefslogtreecommitdiff
path: root/tools/utilities/debug_map_diff.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/utilities/debug_map_diff.py')
-rwxr-xr-xtools/utilities/debug_map_diff.py288
1 files changed, 170 insertions, 118 deletions
diff --git a/tools/utilities/debug_map_diff.py b/tools/utilities/debug_map_diff.py
index 02a7f0cb..64d283bb 100755
--- a/tools/utilities/debug_map_diff.py
+++ b/tools/utilities/debug_map_diff.py
@@ -17,8 +17,8 @@ from collections import defaultdict
arg_parse = argparse.ArgumentParser()
arg_parse.add_argument("object_name", help="Name of the object to compare, e.g. d_a_bridge or d_a_npc_fa1")
args = arg_parse.parse_args()
-object_name: str = args.object_name
-assert "/" not in object_name and "." not in object_name, "The object name should not contain slashes or dots"
+target_object_name: str = args.object_name
+assert "/" not in target_object_name and "." not in target_object_name, "The object name should not contain slashes or dots"
debug_maps_root_path = Path("orig/D44J01/files/maps")
decomp_root_path = Path(".")
@@ -32,7 +32,7 @@ for ninja_target in subprocess.check_output(["ninja", "-t", "targets", "all"]).d
all_ninja_outputs.append(ninja_output)
target_map_path_dol = debug_maps_root_path / "frameworkD.map"
-target_map_path_rel = debug_maps_root_path / f"{object_name}D.map"
+target_map_path_rel = debug_maps_root_path / f"{target_object_name}D.map"
if target_map_path_rel.exists():
target_is_rel = True
target_map_path = target_map_path_rel
@@ -44,7 +44,7 @@ del target_map_path_dol
del target_map_path_rel
base_map_path_dol = decomp_root_path / "build/D44J01/framework.elf.MAP"
-base_map_path_rel = decomp_root_path / f"build/D44J01/{object_name}/{object_name}.plf.MAP"
+base_map_path_rel = decomp_root_path / f"build/D44J01/{target_object_name}/{target_object_name}.plf.MAP"
if str(base_map_path_rel) in all_ninja_outputs:
base_is_rel = True
base_map_path = base_map_path_rel
@@ -59,121 +59,135 @@ retcode = subprocess.call(["ninja", base_map_path.relative_to(decomp_root_path)]
assert retcode == 0, "Ninja build call failed"
class Symbol:
- def __init__(self, name: str, size: int, sym_type: str | None = None, linkage: str | None = None):
+ def __init__(self, name: str, size: int, sym_type: str | None = None, linkage: str | None = None, stripped: bool | None = None):
self.name = name
self.size = size
self.sym_type = sym_type
self.linkage = linkage
+ self.stripped = stripped
+
+ def __repr__(self):
+ return f"Symbol(name={self.name}, size={self.size}, sym_type={self.sym_type}, linkage={self.linkage}, stripped={self.stripped})"
-def get_main_symbols(framework_map_contents: str, valid_obj_names = None):
- symbols = {}
- localstatic_counters = defaultdict(int)
- matches = re.findall(r"^ [0-9a-f]{8} ([0-9a-f]{6}) (?:[0-9a-f]{8})(?: +\d+)? (.+?)(?: \(entry of [^)]+\))? \t(?:\S+\.a )?([^\s\.]+)\.\S+ ?$", framework_map_contents, re.IGNORECASE | re.MULTILINE)
- for match in matches:
- size, symbol_name, obj_name = match
- size = int(size, 16)
-
- if valid_obj_names is not None and obj_name not in valid_obj_names:
- continue
- if symbol_name.startswith("."):
- continue
- if re.search(r"^@\d+$", symbol_name):
- continue
- if localstatic_match := re.search(r"^([^\s\$]+)\$\d+$", symbol_name):
- localstatic_name = localstatic_match.group(1)
- localstatic_counters[localstatic_name] += 1
- symbol_name = f"{localstatic_name}${localstatic_counters[localstatic_name]}"
-
- symbols[symbol_name] = Symbol(symbol_name, size)
- if len(symbols) == 0:
- raise Exception("Failed to find object matching the given name (check for typos)")
- return symbols
-
-def get_rel_symbols(rel_map_data: str):
- rel_map_lines = rel_map_data.splitlines()
+def get_symbols_from_linker_map(map_contents: str, missing_tree_and_stripped=False):
+ map_lines = map_contents.splitlines()
- line = rel_map_lines.pop(0)
- assert line == "Link map of _prolog"
- symbol_name_to_type_linkage_obj = {}
- while True:
- line = rel_map_lines.pop(0)
- if line == "":
- # End of link map
- break
- if line.startswith(">>> SYMBOL NOT FOUND: "):
- continue
- linker_symbol_match = re.search(r"^ +(\d+)\] (\S+) found as linker generated symbol$", line)
- if linker_symbol_match:
- continue
- normal_symbol_match = re.search(r"^ +(\d+)\] (\S+) \((section|func|object|notype),(global|local|weak)\) found in (\S+) $", line)
- assert normal_symbol_match is not None, f"Unknown line: {repr(line)}"
- link_map_depth = int(normal_symbol_match.group(1))
- symbol_name = normal_symbol_match.group(2)
- symbol_type = normal_symbol_match.group(3)
- linkage = normal_symbol_match.group(4)
- object_name = normal_symbol_match.group(5)
- assert symbol_name not in symbol_name_to_type_linkage_obj
- symbol_name_to_type_linkage_obj[symbol_name] = (symbol_type, linkage, object_name)
+ object_name_to_ext = {}
+ def remove_object_ext(object_name: str):
+ object_name, object_ext = object_name.split(".", 1)
+ if object_name in object_name_to_ext:
+ # Check to be sure we never have two objects with the same name but different extensions
+ assert object_name_to_ext[object_name] == object_ext
+ else:
+ object_name_to_ext[object_name] = object_ext
+ return object_name
- # found_memory_map = False
- # next_section_index = 0
- # section_name_to_section_index = {}
- # for line in rel_map_lines:
- # if line.strip() == "Memory map:":
- # found_memory_map = True
- # if found_memory_map:
- # section_match = re.search(r"^ +\.(text|ctors|dtors|rodata|data|bss) [0-9a-f]{8} ([0-9a-f]{8}) [0-9a-f]{8}$", line)
- # if section_match:
- # section_name = section_match.group(1)
- # section_size = int(section_match.group(2), 16)
- # if section_size > 0:
- # section_name_to_section_index[section_name] = next_section_index
- # next_section_index += 1
- # if not found_memory_map:
- # raise Exception("Failed to find memory map")
+ object_name_to_symbol_name_to_type_and_linkage = defaultdict(dict)
+ localstatic_counters = defaultdict(lambda: defaultdict(int))
+ unref_dupe_symbol_names_to_object_name_to_type_linkage = {}
+ if not missing_tree_and_stripped:
+ line = map_lines.pop(0)
+ assert line == "Link map of _prolog" or line == "Link map of __start"
+ curr_unref_dupe_symbol_name = None
+ while True:
+ line = map_lines.pop(0)
+ if line == "":
+ # End of link map
+ break
+
+ unref_dupe_symbol_match = re.search(r"^ +(\d+)\] >>> UNREFERENCED DUPLICATE (\S+)$", line)
+ if unref_dupe_symbol_match:
+ link_map_depth = int(unref_dupe_symbol_match.group(1))
+ curr_unref_dupe_symbol_name = unref_dupe_symbol_match.group(2)
+ assert curr_unref_dupe_symbol_name not in unref_dupe_symbol_names_to_object_name_to_type_linkage
+ unref_dupe_symbol_names_to_object_name_to_type_linkage[curr_unref_dupe_symbol_name] = {}
+ continue
+ unref_dupe_linkage_match = re.search(r"^ +(\d+)\] >>> \((func|object),(weak)\) found in (\S+) $", line)
+ if unref_dupe_linkage_match:
+ assert curr_unref_dupe_symbol_name is not None
+ link_map_depth = int(unref_dupe_linkage_match.group(1))
+ symbol_type = unref_dupe_linkage_match.group(2)
+ linkage = unref_dupe_linkage_match.group(3)
+ object_name = remove_object_ext(unref_dupe_linkage_match.group(4))
+
+ # A symbol shouldn't be duplicates multiple times in the same object, but it's possible for multiple objects to have the exact same name.
+ # e.g. getRaw__Q37JGadget6binary10TParseDataCFv is in 5 different TUs named control.o
+ # Not really any way to detect this, so just ignore it and check that the types all match.
+ # assert object_name not in unref_dupe_symbol_names_to_object_name_to_type_linkage[curr_unref_dupe_symbol_name]
+ if object_name in unref_dupe_symbol_names_to_object_name_to_type_linkage[curr_unref_dupe_symbol_name]:
+ assert (symbol_type, linkage) == unref_dupe_symbol_names_to_object_name_to_type_linkage[curr_unref_dupe_symbol_name][object_name]
+
+ unref_dupe_symbol_names_to_object_name_to_type_linkage[curr_unref_dupe_symbol_name][object_name] = (symbol_type, linkage)
+ continue
+ else:
+ curr_unref_dupe_symbol_name = None
+
+ if line.startswith(">>> SYMBOL NOT FOUND: "):
+ continue
+ linker_symbol_match = re.search(r"^ +(\d+)\] (\S+) found as linker generated symbol$", line)
+ if linker_symbol_match:
+ continue
+ normal_symbol_match = re.search(r"^ +(\d+)\] (\S+) \((section|func|object|notype),(global|local|weak)\) found in (\S+) $", line)
+ assert normal_symbol_match is not None, f"Unknown line: {repr(line)}"
+ link_map_depth = int(normal_symbol_match.group(1))
+ symbol_name = normal_symbol_match.group(2)
+ symbol_type = normal_symbol_match.group(3)
+ linkage = normal_symbol_match.group(4)
+ object_name = remove_object_ext(normal_symbol_match.group(5))
+
+ if localstatic_match := re.search(r"^([^\s\$]+)\$\d+$", symbol_name):
+ localstatic_name = localstatic_match.group(1)
+ localstatic_counters[object_name][localstatic_name] += 1
+ symbol_name = f"{localstatic_name}${localstatic_counters[object_name][localstatic_name]}"
+
+ assert symbol_name not in object_name_to_symbol_name_to_type_and_linkage[object_name], f"Duplicate symbol: {repr(symbol_name)}"
+ object_name_to_symbol_name_to_type_and_linkage[object_name][symbol_name] = (symbol_type, linkage)
- symbols = {}
- # current_section_name = None
- localstatic_counters = defaultdict(int)
- for line in rel_map_lines:
- # section_header_match = re.search(r"^\.(text|ctors|dtors|rodata|data|bss) section layout$", line)
- # if section_header_match:
- # current_section_name = section_header_match.group(1)
- # if current_section_name != "text":
- # continue
+ symbols: dict[str, dict[str, Symbol]] = defaultdict(dict)
+ localstatic_counters = defaultdict(lambda: defaultdict(int))
+ unref_dupe_symbol_names_already_added = set()
+ for line in map_lines:
symbol_entry_match = re.search(r"^ ([0-9a-f]{8}|UNUSED ) ([0-9a-f]{6}) ([0-9a-f]{8}|\.{8})(?: +\d+)? (.+?)(?: \(entry of [^)]+\))? \t?(\S+)", line, re.IGNORECASE)
if symbol_entry_match:
- symbol_address = symbol_entry_match.group(1)
+ symbol_offset = symbol_entry_match.group(1)
symbol_size = symbol_entry_match.group(2)
symbol_size = int(symbol_size, 16)
- symbol_offset = symbol_entry_match.group(3)
- if symbol_address == "UNUSED ":
- assert symbol_offset == "........"
- if symbol_offset == "........":
- assert symbol_address == "UNUSED "
- symbol_offset = None
+ symbol_address = symbol_entry_match.group(3)
+ if symbol_offset == "UNUSED ":
+ assert symbol_address == "........"
+ if symbol_address == "........":
+ assert symbol_offset == "UNUSED "
+ symbol_address = None
+ stripped = True
else:
- symbol_offset = int(symbol_offset, 16)
+ symbol_address = int(symbol_address, 16)
+ stripped = False
symbol_name = symbol_entry_match.group(4)
- object_name = symbol_entry_match.group(5)
+ object_name = remove_object_ext(symbol_entry_match.group(5))
- if object_name in ["executor.o", "global_destructor_chain.o"]:
+ if symbol_name.startswith(".") or symbol_name in ["extab", "extabindex"]:
+ # e.g. Section symbol (.text) or pool symbol (...data)
continue
if re.search(r"^@\d+$", symbol_name):
continue
if localstatic_match := re.search(r"^([^\s\$]+)\$\d+$", symbol_name):
localstatic_name = localstatic_match.group(1)
- localstatic_counters[localstatic_name] += 1
- symbol_name = f"{localstatic_name}${localstatic_counters[localstatic_name]}"
+ localstatic_counters[object_name][localstatic_name] += 1
+ symbol_name = f"{localstatic_name}${localstatic_counters[object_name][localstatic_name]}"
- if symbol_name in symbol_name_to_type_linkage_obj:
- symbol_type, linkage, object_name = symbol_name_to_type_linkage_obj[symbol_name]
+ if symbol_name in object_name_to_symbol_name_to_type_and_linkage[object_name]:
+ symbol_type, linkage = object_name_to_symbol_name_to_type_and_linkage[object_name][symbol_name]
else:
- symbol_type = linkage = object_name = None
- symbols[symbol_name] = Symbol(symbol_name, symbol_size, sym_type=symbol_type, linkage=linkage)
- #print("%08X %s" % (symbol_offset, symbol_name))
-
- #print(rel_symbol_names)
+ assert stripped or missing_tree_and_stripped, f"Symbol {repr(symbol_name)} is missing linkage information in object {repr(object_name)}"
+ symbol_type = linkage = None
+ symbols[object_name][symbol_name] = Symbol(symbol_name, symbol_size, sym_type=symbol_type, linkage=linkage, stripped=stripped)
+
+ if symbol_name in unref_dupe_symbol_names_to_object_name_to_type_linkage and symbol_name not in unref_dupe_symbol_names_already_added:
+ for other_object_name in unref_dupe_symbol_names_to_object_name_to_type_linkage[symbol_name]:
+ symbol_type, linkage = unref_dupe_symbol_names_to_object_name_to_type_linkage[symbol_name][other_object_name]
+ assert stripped == False, "Shouldn't reach a stripped duplicate here"
+ symbols[other_object_name][symbol_name] = Symbol(symbol_name, symbol_size, sym_type=symbol_type, linkage=linkage, stripped=stripped)
+ unref_dupe_symbol_names_already_added.add(symbol_name)
return symbols
@@ -182,21 +196,35 @@ def is_debug_only_symbol(symbol_name: str):
return True
return False
-if target_is_rel:
- target_symbols = get_rel_symbols(target_map_path.read_text())
-else:
- target_symbols = get_main_symbols(target_map_path.read_text(), valid_obj_names=[object_name])
+target_missing_tree_and_stripped = not target_is_rel
-if base_is_rel:
- base_symbols = get_rel_symbols(base_map_path.read_text())
-else:
- base_symbols = get_main_symbols(base_map_path.read_text(), valid_obj_names=[object_name])
+all_target_symbols = get_symbols_from_linker_map(target_map_path.read_text(), missing_tree_and_stripped=target_missing_tree_and_stripped)
+target_symbols = all_target_symbols[target_object_name]
+if len(target_symbols) == 0:
+ raise Exception("Failed to find object matching the given name (check for typos)")
+
+all_base_symbols = get_symbols_from_linker_map(base_map_path.read_text())
+base_symbols = all_base_symbols[target_object_name]
+if len(base_symbols) == 0:
+ raise Exception("Failed to find object matching the given name (check for typos)")
print(len(target_symbols), len(base_symbols))
+target_symbol_names_in_previous_objects = set()
+if target_missing_tree_and_stripped:
+ # This handles the logic for checking if a symbol already appeared earlier on in framework.map.
+ # Note: I'm not sure if the logic here is 100% accurate for edge cases since it just relies on dict insertion order.
+ # Might need to add more robust logic later that keeps track of symbol section, address, or line number within the map file...?
+ for object_name, symbols in all_target_symbols.items():
+ if object_name == target_object_name:
+ break
+ for symbol_name, symbol in symbols.items():
+ target_symbol_names_in_previous_objects.add(symbol.name)
+
symbol_size_diffs = []
total_missing = 0
total_fake = 0
+total_maybe_fake = 0
total_right_size = 0
total_wrong_size = 0
total_wrong_linkage = 0
@@ -210,7 +238,7 @@ for symbol_name, target_symbol in target_symbols.items():
base_size = base_symbols[symbol_name].size
size_diff = abs(target_symbol.size - base_size)
ratio = size_diff / target_symbol.size
- if size_diff != 0:
+ if symbol_name in base_symbols and size_diff != 0:
total_wrong_size += 1
symbol_size_diffs.append((symbol_name, target_symbol.size, base_size, ratio))
@@ -231,11 +259,23 @@ for symbol_name, target_size, base_size, ratio in symbol_size_diffs:
prefix = "WRONG: "
print(prefix + symbol_name, "0x%X" % target_size, "0x%X" % base_size, ratio)
+maybe_fake_symbols = []
+fake_symbols = []
for symbol_name, base_symbol in base_symbols.items():
if symbol_name in target_symbols:
continue
- print("FAKE:", symbol_name, "0x%X" % base_symbol.size)
- total_fake += 1
+ if target_missing_tree_and_stripped and (base_symbol.stripped or symbol_name in target_symbol_names_in_previous_objects):
+ maybe_fake_symbols.append(base_symbol)
+ else:
+ fake_symbols.append(base_symbol)
+
+for base_symbol in maybe_fake_symbols:
+ print("FAKE?:", base_symbol.name, "0x%X" % base_symbol.size)
+ total_maybe_fake += 1
+
+for base_symbol in fake_symbols:
+ print("FAKE:", base_symbol.name, "0x%X" % base_symbol.size)
+ total_fake += 1
for symbol_name, target_size, base_size, ratio in symbol_size_diffs:
prefix = ""
@@ -246,20 +286,32 @@ for symbol_name, target_size, base_size, ratio in symbol_size_diffs:
total_missing += 1
print(prefix + symbol_name, "0x%X" % target_size)
-if target_is_rel:
- for symbol_name, target_symbol in target_symbols.items():
- if target_symbol.size == 0:
- continue
- if symbol_name not in base_symbols:
- continue
- base_symbol = base_symbols[symbol_name]
- if target_symbol.linkage != base_symbol.linkage:
- total_wrong_linkage += 1
- print(f"LINKAGE: {symbol_name} (should be {target_symbol.linkage}, is {base_symbol.linkage})")
+for symbol_name, target_symbol in target_symbols.items():
+ if target_symbol.size == 0:
+ continue
+ if symbol_name not in base_symbols:
+ continue
+ base_symbol = base_symbols[symbol_name]
+ wrong_linkage = False
+ if target_symbol.linkage is None and base_symbol.sym_type == "object":
+ # The official framework.map for main.dol doesn't include linkage, but we can guess it based off of certain symbol name prefixes.
+ if base_symbol.name.startswith("l_") and base_symbol.linkage != "local":
+ wrong_linkage = True
+ target_linkage = "local"
+ elif base_symbol.name.startswith("g_") and base_symbol.linkage != "global":
+ wrong_linkage = True
+ target_linkage = "global"
+ elif target_symbol.linkage is not None and target_symbol.linkage != base_symbol.linkage:
+ wrong_linkage = True
+ target_linkage = target_symbol.linkage
+ if wrong_linkage:
+ total_wrong_linkage += 1
+ print(f"LINKAGE: {symbol_name} (should be {target_linkage}, is {base_symbol.linkage})")
print("==================================================")
print(f"Total right size: {total_right_size}")
print(f"Total wrong size: {total_wrong_size}")
print(f"Total fake: {total_fake}")
+print(f"Total maybe fake: {total_maybe_fake}")
print(f"Total missing: {total_missing}")
print(f"Total wrong linkage: {total_wrong_linkage}")