diff --git a/tools/roadmap/roadmap.py b/tools/roadmap/roadmap.py index de1b2c10..5c1c91ed 100644 --- a/tools/roadmap/roadmap.py +++ b/tools/roadmap/roadmap.py @@ -29,6 +29,7 @@ class ModuleMap: def __init__(self, pdb, binfile) -> None: cvdump = Cvdump(pdb).section_contributions().modules().run() self.module_lookup = {m.id: (m.lib, m.obj) for m in cvdump.modules} + self.library_lookup = {m.obj: m.lib for m in cvdump.modules} self.section_contrib = [ ( binfile.get_abs_addr(sizeref.section, sizeref.offset), @@ -39,6 +40,9 @@ def __init__(self, pdb, binfile) -> None: if binfile.is_valid_section(sizeref.section) ] + def get_lib_for_module(self, module: str) -> Optional[str]: + return self.library_lookup.get(module) + def get_all_cmake_modules(self) -> List[str]: return [ obj @@ -77,6 +81,15 @@ def match_type_abbreviation(mtype: Optional[SymbolType]) -> str: return mtype.name.lower()[:3] +def get_cmakefiles_prefix(module: str) -> str: + """For the given .obj, get the "CMakeFiles/something.dir/" prefix. + For lack of a better option, this is the library for this module.""" + if module.startswith("CMakeFiles"): + return "/".join(module.split("/", 2)[:2]) + "/" + + return module + + def truncate_module_name(prefix: str, module: str) -> str: """Remove the CMakeFiles prefix and the .obj suffix for the given module. Input: CMakeFiles/lego1.dir/, CMakeFiles/lego1.dir/LEGO1/define.cpp.obj @@ -173,27 +186,33 @@ def iter_sorted(self) -> Iterator[Tuple[int, int]]: yield (avg, mod) -def suggest_order(results: List[RoadmapRow], cmake_modules: List[str], match_type: str): +def suggest_order(results: List[RoadmapRow], module_map: ModuleMap, match_type: str): """Suggest the order of modules for CMakeLists.txt""" dc = DeltaCollector(match_type) for row in results: dc.read_row(row) - leftover_modules = set(cmake_modules) + # First, show the order of .obj files for the "CMake Modules" + # Meaning: the modules where the .obj file begins with "CMakeFiles". + # These are the libraries where we directly control the order. + # The library name (from cvdump) doesn't make it obvious that these are + # our libraries so we derive the name based on the CMakeFiles prefix. + leftover_modules = set(module_map.get_all_cmake_modules()) # A little convoluted, but we want to take the first two tokens # of the string with '/' as the delimiter. # i.e. CMakeFiles/isle.dir/ # The idea is to print exactly what appears in CMakeLists.txt. - cmake_prefixes = sorted( - set("/".join(mod.split("/", 2)[:2]) + "/" for mod in leftover_modules) - ) + cmake_prefixes = sorted(set(get_cmakefiles_prefix(mod) for mod in leftover_modules)) + # Save this off because we'll use it again later. computed_order = list(dc.iter_sorted()) for prefix in cmake_prefixes: print(prefix) + + last_earliest = 0 # Show modules ordered by the computed average of addresses for _, module in computed_order: if not module.startswith(prefix): @@ -206,16 +225,22 @@ def suggest_order(results: List[RoadmapRow], cmake_modules: List[str], match_typ if displacements is not None and len(displacements) > 0: avg_displacement = int(statistics.mean(displacements)) + # Call attention to any modules where ordering by earliest + # address is different from the computed order we display. earliest = dc.earliest.get(module) + ooo_mark = "*" if earliest < last_earliest else " " + last_earliest = earliest + code_file = truncate_module_name(prefix, module) - print(f"0x{earliest:08x} {avg_displacement:10} {code_file}") + print(f"0x{earliest:08x}{ooo_mark} {avg_displacement:10} {code_file}") # These modules are included in the final binary (in some form) but - # they are not represented by whichever type of symbol we were checking. - # n.b. There could still be other modules that are part of CMakeLists.txt - # but are not included in the pdb for whatever reason. - # In other words: don't take the list we provide as the final word on what - # should or should not be included. This is merely a suggestion of the order. + # don't contribute any symbols of the type we are checking. + # n.b. There could still be other modules that are part of + # CMakeLists.txt but are not included in the pdb for whatever reason. + # In other words: don't take the list we provide as the final word on + # what should or should not be included. + # This is merely a suggestion of the order. for module in leftover_modules: if not module.startswith(prefix): continue @@ -226,6 +251,25 @@ def suggest_order(results: List[RoadmapRow], cmake_modules: List[str], match_typ print() + # Now display the order of all libaries in the final file. + library_order = {} + + for module, start in dc.earliest.items(): + lib = module_map.get_lib_for_module(module) + if lib is None: + lib = get_cmakefiles_prefix(module) + + if start < library_order.get(lib, 0xFFFFFFFFF): + library_order[lib] = start + + print("Library order (average address shown):") + for lib, start in sorted(library_order.items(), key=lambda x: x[1]): + # Strip off any OS path for brevity + if not lib.startswith("CMakeFiles"): + lib = os.path.basename(lib) + + print(f"{lib:40} {start:08x}") + def print_text_report(results: List[RoadmapRow]): """Print the result with original and recomp addresses.""" @@ -398,7 +442,7 @@ def to_roadmap_row(match): results = list(map(to_roadmap_row, engine.get_all())) if args.order is not None: - suggest_order(results, module_map.get_all_cmake_modules(), args.order) + suggest_order(results, module_map, args.order) return if args.csv is None: