├── .gitignore ├── .gitmodules ├── LICENSE ├── add_missing_functions.py ├── diff_settings.py ├── ghidra_scripts ├── DWARF.java ├── README.md └── RenameFunctionsInGhidra.java ├── ida_remove_function_tails.py ├── identify_matching_functions.py ├── identify_matching_functions_by_call.py ├── identify_matching_rtti_functions.py ├── progress.py ├── rename_functions_in_ida.py ├── setup_common.py ├── show_vtable.py ├── translate_ida_types.py ├── util ├── __init__.py ├── checker.py ├── config.py ├── dsym.py ├── elf.py ├── graph.py ├── tools.py └── utils.py └── viking ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE └── src ├── capstone_utils.rs ├── checks.rs ├── elf.rs ├── functions.rs ├── lib.rs ├── repo.rs ├── tools ├── check.rs ├── decompme.rs └── list_symbols.rs └── ui.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.pyc 4 | *.egg-info/ 5 | *.dist-info/ 6 | .mypy_cache/ 7 | .benchmarks/ 8 | 9 | # IDEs 10 | .idea/ 11 | .vscode/ 12 | .DS_Store 13 | 14 | # IDA 15 | *.id0 16 | *.id1 17 | *.id2 18 | *.idb 19 | *.i64 20 | *.nam 21 | *.til 22 | 23 | # Ghidra 24 | *.gpr 25 | *.rep/ 26 | *.lock 27 | 28 | # Debugging 29 | perf.data 30 | perf.data.old 31 | .gdb_history 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nx-decomp-tools-binaries"] 2 | path = nx-decomp-tools-binaries 3 | url = https://github.com/open-ead/nx-decomp-tools-binaries 4 | [submodule "asm-differ"] 5 | path = asm-differ 6 | url = https://github.com/simonlindholm/asm-differ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 leoetlino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /add_missing_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import csv 5 | import sys 6 | from pathlib import Path 7 | from typing import Dict, Set, List 8 | 9 | from util import utils 10 | 11 | 12 | def main() -> None: 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument("csv_path", help="Path to function CSV to merge") 15 | args = parser.parse_args() 16 | 17 | csv_path = Path(args.csv_path) 18 | 19 | known_fn_addrs: Set[int] = {func.addr for func in utils.get_functions(all=True)} 20 | names: Dict[int, str] = {func.addr: func.name for func in utils.get_functions(all=True)} 21 | new_fns: List[utils.FunctionInfo] = [] 22 | for func in utils.get_functions(csv_path, all=True): 23 | if func.addr in known_fn_addrs: 24 | if not names[func.addr].startswith("_") and not func.name.startswith("_Z"): 25 | names[func.addr] = func.name 26 | else: 27 | new_fns.append(func) 28 | 29 | new_fn_list: List[utils.FunctionInfo] = [] 30 | new_fn_list.extend(utils.get_functions(all=True)) 31 | new_fn_list.extend(new_fns) 32 | new_fn_list.sort(key=lambda func: func.addr) 33 | 34 | # Output the modified function CSV. 35 | writer = csv.writer(sys.stdout, lineterminator="\n") 36 | writer.writerow("Address,Quality,Size,Name".split(",")) 37 | for func in new_fn_list: 38 | if func.addr in names: 39 | func.raw_row[3] = names[func.addr] 40 | writer.writerow(func.raw_row) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /diff_settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import platform 3 | import util.config 4 | from util.tools import find_tool 5 | 6 | def add_custom_arguments(parser): 7 | parser.add_argument('--version', dest='version', help='Specify which version should be recompiled on source changes') 8 | 9 | 10 | def apply(config, args): 11 | root = util.config.get_repo_root() 12 | 13 | version = None 14 | if isinstance(args, dict): 15 | version = args.get("version") 16 | else: 17 | try: 18 | version = args.version 19 | except AttributeError: 20 | pass 21 | if version is None: 22 | version = util.config.get_default_version() 23 | 24 | config['arch'] = 'aarch64' 25 | config['baseimg'] = util.config.get_base_elf(version) 26 | config['myimg'] = util.config.get_decomp_elf(version) 27 | config['source_directories'] = [str(root / 'src'), str(root / 'lib')] 28 | config['objdump_executable'] = find_tool('llvm-objdump') 29 | # ill-suited to C++ projects (and too slow for large executables) 30 | config['show_line_numbers_default'] = False 31 | for dir in (root / 'build', root / 'build/nx64-release'): 32 | if (dir / 'build.ninja').is_file(): 33 | config['make_command'] = ['ninja', '-C', str(dir)] 34 | 35 | if version is not None: 36 | dir = root / 'build' / version 37 | if (dir / 'build.ninja').is_file(): 38 | config['make_command'] = ['ninja', '-C', str(dir)] 39 | 40 | 41 | def map_build_target(make_target: str): 42 | if make_target == util.config.get_decomp_elf(): 43 | return util.config.get_build_target() 44 | 45 | # TODO: When support for directly diffing object files is added, this needs to strip 46 | # the build/ prefix from the object file targets. 47 | return make_target 48 | -------------------------------------------------------------------------------- /ghidra_scripts/DWARF.java: -------------------------------------------------------------------------------- 1 | // Imports DWARF information from the compiled version of a program. 2 | // To use in decompilation efforts, drag and drop from the compiled version's Data Type Manager, into the original Switch version's. 3 | // This script is very similar to Ghidra's "DWARF_ExtractorScript.java", but disables function-related generation for speed and clarity. 4 | //@author OpenEAD 5 | //@category NX-Switch 6 | 7 | import ghidra.app.script.GhidraScript; 8 | import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram; 9 | import ghidra.app.util.bin.format.dwarf4.next.DWARFImportOptions; 10 | import ghidra.app.util.bin.format.dwarf4.next.DWARFParser; 11 | import ghidra.app.util.bin.format.dwarf4.next.DWARFImportSummary; 12 | import ghidra.program.model.data.BuiltInDataTypeManager; 13 | 14 | public class DWARF extends GhidraScript { 15 | @Override 16 | public void run() throws Exception { 17 | if (!DWARFProgram.isDWARF(currentProgram)) { 18 | popup("Unable to find DWARF information, aborting"); 19 | return; 20 | } 21 | 22 | DWARFImportOptions importOptions = new DWARFImportOptions(); 23 | 24 | // These clutter the types, and take ages to generate. 25 | // That's why they're disabled. 26 | importOptions.setCreateFuncSignatures(false); 27 | importOptions.setImportFuncs(false); 28 | 29 | // Default is too low 30 | importOptions.setImportLimitDIECount(Integer.MAX_VALUE); 31 | 32 | try (DWARFProgram dwarfProg = new DWARFProgram(currentProgram, importOptions, monitor)) { 33 | BuiltInDataTypeManager dtms = BuiltInDataTypeManager.getDataTypeManager(); 34 | DWARFParser dp = new DWARFParser(dwarfProg, dtms, monitor); 35 | DWARFImportSummary importSummary = dp.parse(); 36 | importSummary.logSummaryResults(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ghidra_scripts/README.md: -------------------------------------------------------------------------------- 1 | # Usage instructions: 2 | 1. Open the `Script Manager` (green play icon) from Ghidra's toolbar. 3 | 2. Click on `Manage Script Directories` (bullet points icon) inside the Script Manager, and add this directory. 4 | 3. Select the `NX-Switch` category, and use the scripts provided. Information and instructions for each individual script are shown inside the Script Manager. 5 | -------------------------------------------------------------------------------- /ghidra_scripts/RenameFunctionsInGhidra.java: -------------------------------------------------------------------------------- 1 | // Script to import Switch decompilation CSV data into Ghidra. 2 | // Automatically demangles and adds tags for decompilation status. 3 | // WARNING: This MUST be run for the first time BEFORE analysis is applied. 4 | // You can run the script multiple times to update names and decompilation status, 5 | // though it may not always work as expected. If you notice any problems, please submit a bug! 6 | //@author OpenEAD 7 | //@category NX-Switch 8 | 9 | import ghidra.app.script.GhidraScript; 10 | import ghidra.program.model.symbol.SourceType; 11 | import java.io.BufferedReader; 12 | import java.io.FileReader; 13 | import java.io.File; 14 | import ghidra.program.model.address.Address; 15 | import ghidra.program.model.listing.Function; 16 | import ghidra.program.model.listing.FunctionManager; 17 | import ghidra.program.model.address.AddressSet; 18 | import ghidra.util.NumericUtilities; 19 | import ghidra.app.cmd.label.DemanglerCmd; 20 | import ghidra.program.model.listing.FunctionTag; 21 | import ghidra.util.exception.DuplicateNameException; 22 | import ghidra.program.model.listing.FunctionTagManager; 23 | import java.util.stream.Stream; 24 | 25 | public class RenameFunctionsInGhidra extends GhidraScript { 26 | private String[] bad_prefixes = { "sub_", "nullsub_", "j_" }; 27 | private FunctionManager func_mgr; 28 | private FunctionTagManager func_tag_mgr; 29 | private String ok; 30 | private String minor; 31 | private String major; 32 | private String wip; 33 | private String undecompiled; 34 | private String lib; 35 | 36 | private FunctionTag getOrMake(String name) { 37 | FunctionTag f = func_tag_mgr.getFunctionTag(name); 38 | if (f == null) f = func_tag_mgr.createFunctionTag(name, null); 39 | return f; 40 | } 41 | 42 | @Override 43 | public void run() throws Exception { 44 | func_mgr = currentProgram.getFunctionManager(); 45 | func_tag_mgr = func_mgr.getFunctionTagManager(); 46 | ok = getOrMake("OK").getName(); 47 | minor = getOrMake("MINOR").getName(); 48 | major = getOrMake("MAJOR").getName(); 49 | wip = getOrMake("WIP").getName(); 50 | undecompiled = getOrMake("UNDECOMPILED").getName(); 51 | lib = getOrMake("LIBRARY").getName(); 52 | 53 | File input_csv = askFile("functions.csv", "Go"); 54 | try (BufferedReader br = new BufferedReader(new FileReader(input_csv))) { 55 | // Skip header 56 | String line = br.readLine(); 57 | while ((line = br.readLine()) != null) { 58 | // Don't skip empty last column 59 | String[] pieces = line.split(",", -4); 60 | if (pieces.length != 4) throw new Exception("Invalid CSV row: " + line); 61 | 62 | Address addr = toAddr(pieces[0]); 63 | String status = pieces[1]; 64 | long func_size = func_size = NumericUtilities.parseLong(pieces[2].strip()); 65 | 66 | String name = pieces[3].strip(); 67 | 68 | Function func = applyFunction(addr, status, name, func_size); 69 | } 70 | } 71 | } 72 | 73 | 74 | private Function applyFunction(Address addr, String status, String name, long func_size) throws Exception { 75 | if (name.isEmpty() || Stream.of(bad_prefixes).anyMatch(name::startsWith)) 76 | name = null; 77 | 78 | Function func = func_mgr.getFunctionAt(addr); 79 | AddressSet body = new AddressSet(addr, addr.addNoWrap(func_size - 1)); 80 | 81 | if (func != null) { 82 | if (name != null) { 83 | try { 84 | func.getSymbol().setNameAndNamespace(name, currentProgram.getGlobalNamespace(), SourceType.IMPORTED); 85 | } catch (DuplicateNameException e) {} 86 | } 87 | if (!func.getBody().hasSameAddresses(body)) { 88 | println("A function was detected with the range " + func.getBody().toString() + " but should have the range " + body.toString() + "."); 89 | } 90 | } else { 91 | func = func_mgr.createFunction(name, addr, body, SourceType.IMPORTED); 92 | } 93 | 94 | if (name != null) { 95 | new DemanglerCmd(addr, name).applyTo(currentProgram, monitor); 96 | } 97 | 98 | func.removeTag(ok); 99 | func.removeTag(minor); 100 | func.removeTag(major); 101 | func.removeTag(wip); 102 | func.removeTag(undecompiled); 103 | func.removeTag(lib); 104 | if (status.equals("O")) { 105 | func.addTag(ok); 106 | } else if (status.equals("m")) { 107 | func.addTag(minor); 108 | } else if (status.equals("M")) { 109 | func.addTag(major); 110 | } else if (status.equals("W")) { 111 | func.addTag(wip); 112 | } else if (status.equals("L")) { 113 | func.addTag(lib); 114 | func.addTag(undecompiled); 115 | } else { 116 | func.addTag(undecompiled); 117 | } 118 | 119 | return func; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ida_remove_function_tails.py: -------------------------------------------------------------------------------- 1 | import idaapi 2 | 3 | for i in range(idaapi.get_fchunk_qty()): 4 | chunk = idaapi.getn_fchunk(i) 5 | if not idaapi.is_func_tail(chunk): 6 | continue 7 | 8 | ea = chunk.start_ea 9 | print("removing tail 0x%016x" % ea) 10 | parent = idaapi.get_func(ea) 11 | idaapi.remove_func_tail(parent, ea) 12 | idaapi.add_func(ea) 13 | -------------------------------------------------------------------------------- /identify_matching_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | from colorama import Fore 5 | import csv 6 | import sys 7 | from pathlib import Path 8 | from typing import Dict 9 | 10 | import util.checker 11 | import util.elf 12 | from util import utils 13 | 14 | 15 | def read_candidates(path: Path) -> Dict[str, util.elf.Function]: 16 | candidates: Dict[str, util.elf.Function] = dict() 17 | 18 | for candidate in path.read_text().splitlines(): 19 | columns = candidate.split() 20 | if len(columns) == 3: 21 | candidate = columns[2] 22 | 23 | candidates[candidate] = util.elf.get_fn_from_my_elf(candidate) 24 | 25 | return candidates 26 | 27 | 28 | def main() -> None: 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument("csv_path", 31 | help="Path to a list of functions to identify (in the same format as the main function CSV)") 32 | parser.add_argument("candidates_path", 33 | help="Path to a list of candidates (names only)") 34 | args = parser.parse_args() 35 | 36 | csv_path = Path(args.csv_path) 37 | candidates_path = Path(args.candidates_path) 38 | 39 | candidates = read_candidates(candidates_path) 40 | 41 | new_matches: Dict[int, str] = dict() 42 | checker = util.checker.FunctionChecker() 43 | 44 | # Given a list L of functions to identify and a small list of candidates C, this tool will attempt to 45 | # automatically identify matches by checking each function in L against each function in C. 46 | # 47 | # This matching algorithm is quite naive (quadratic time complexity if both lists have about the same size) 48 | # but this should work well enough for short lists of candidates... 49 | for func in utils.get_functions(csv_path): 50 | if func.status != utils.FunctionStatus.NotDecompiled: 51 | continue 52 | 53 | match_name = "" 54 | 55 | for candidate_name, candidate in candidates.items(): 56 | if len(candidate.data) != func.size: 57 | continue 58 | if checker.check(util.elf.get_fn_from_base_elf(func.addr, func.size), candidate): 59 | match_name = candidate_name 60 | break 61 | 62 | if match_name: 63 | new_matches[func.addr] = match_name 64 | utils.print_note( 65 | f"found new match: {Fore.BLUE}{match_name}{Fore.RESET} ({func.addr | 0x71_00000000:#018x})") 66 | # This is no longer a candidate. 67 | del candidates[match_name] 68 | else: 69 | utils.warn(f"no match found for {Fore.BLUE}{func.name}{Fore.RESET} ({func.addr | 0x71_00000000:#018x})") 70 | 71 | # Output the modified function CSV. 72 | writer = csv.writer(sys.stdout, lineterminator="\n") 73 | for func in utils.get_functions(): 74 | if func.status == utils.FunctionStatus.NotDecompiled and func.addr in new_matches: 75 | func.raw_row[3] = new_matches[func.addr] 76 | writer.writerow(func.raw_row) 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /identify_matching_functions_by_call.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from typing import Dict, List 3 | import argparse 4 | 5 | import cxxfilt 6 | from colorama import Fore 7 | 8 | from util import utils, checker, elf 9 | 10 | 11 | class Checker(checker.FunctionChecker): 12 | def __init__(self): 13 | super().__init__() 14 | self.checking = "" 15 | self.invalid_call_descriptions: List[str] = [] 16 | self.addr_to_symbol = elf.build_addr_to_symbol_table(elf.my_symtab) 17 | self._possible_calls: Dict[int, int] = dict() 18 | 19 | def reset(self) -> None: 20 | self._possible_calls.clear() 21 | 22 | def get_possible_calls(self) -> Dict[int, int]: 23 | return self._possible_calls 24 | 25 | def on_unknown_fn_call(self, orig_addr: int, decomp_addr: int): 26 | existing_addr = self._possible_calls.get(orig_addr) 27 | if existing_addr is not None and existing_addr != decomp_addr: 28 | self.invalid_call_descriptions.append( 29 | f"{orig_addr | 0x7100000000:#x} was mapped to {self.addr_to_symbol[existing_addr]} " 30 | f"({existing_addr:#x}) " 31 | f"but now maps to {self.addr_to_symbol[decomp_addr]} ({decomp_addr:#x})" 32 | f" (while checking {self.checking})") 33 | return 34 | self._possible_calls[orig_addr] = decomp_addr 35 | 36 | 37 | def main() -> None: 38 | parser = argparse.ArgumentParser("Identifies matching functions by looking at function calls in matching functions") 39 | parser.add_argument("-f", "--fn", help="Functions to analyze", nargs="*") 40 | args = parser.parse_args() 41 | 42 | functions_to_analyze = set(args.fn) if args.fn else set() 43 | 44 | functions_by_addr: Dict[int, utils.FunctionInfo] = {fn.addr: fn for fn in utils.get_functions()} 45 | fn_checker = Checker() 46 | for fn in functions_by_addr.values(): 47 | if functions_to_analyze and fn.decomp_name not in functions_to_analyze: 48 | continue 49 | 50 | if fn.status != utils.FunctionStatus.Matching: 51 | continue 52 | 53 | base_fn = elf.get_fn_from_base_elf(fn.addr, fn.size) 54 | try: 55 | my_fn = elf.get_fn_from_my_elf(fn.decomp_name) 56 | except KeyError: 57 | utils.warn(f"could not find function {fn.decomp_name}") 58 | continue 59 | 60 | fn_checker.checking = fn.decomp_name 61 | fn_checker.check(base_fn, my_fn) 62 | 63 | if fn_checker.invalid_call_descriptions: 64 | for x in fn_checker.invalid_call_descriptions: 65 | utils.print_note(x) 66 | utils.fail("invalid calls detected") 67 | 68 | new_matches: Dict[int, str] = dict() 69 | calls = fn_checker.get_possible_calls().copy() 70 | for base_target, my_target in calls.items(): 71 | target_info = functions_by_addr.get(base_target) 72 | if target_info is None: 73 | continue 74 | if target_info.status != utils.FunctionStatus.NotDecompiled: 75 | continue 76 | 77 | base_fn = elf.get_fn_from_base_elf(target_info.addr, target_info.size) 78 | try: 79 | name = fn_checker.addr_to_symbol[my_target] 80 | my_fn = elf.get_fn_from_my_elf(name) 81 | except KeyError: 82 | continue 83 | 84 | if fn_checker.check(base_fn, my_fn): 85 | new_matches[base_target] = name 86 | utils.print_note(f"new match: {Fore.BLUE}{cxxfilt.demangle(name)}{Fore.RESET}") 87 | 88 | utils.add_decompiled_functions(new_matches) 89 | 90 | 91 | if __name__ == '__main__': 92 | main() 93 | -------------------------------------------------------------------------------- /identify_matching_rtti_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import struct 3 | from typing import Dict, Set 4 | 5 | import capstone as cs 6 | import cxxfilt 7 | from colorama import Fore 8 | 9 | from util import utils, elf 10 | 11 | 12 | def main() -> None: 13 | new_matches: Dict[int, str] = dict() 14 | functions_by_addr: Dict[int, utils.FunctionInfo] = {fn.addr: fn for fn in utils.get_functions()} 15 | 16 | md = cs.Cs(cs.CS_ARCH_ARM64, cs.CS_MODE_ARM) 17 | md.detail = True 18 | decomp_addr_to_symbol = elf.build_addr_to_symbol_table(elf.my_symtab) 19 | decomp_glob_data_table = elf.build_glob_data_table(elf.my_elf) 20 | 21 | processed: Set[int] = set() 22 | for fn in functions_by_addr.values(): 23 | if fn.status != utils.FunctionStatus.Matching: 24 | continue 25 | 26 | if fn.size != 0x5C or (not fn.decomp_name.endswith("8getRuntimeTypeInfoEv") and not fn.name.endswith("rtti2")): 27 | continue 28 | 29 | base_fn = elf.get_fn_from_base_elf(fn.addr, fn.size) 30 | try: 31 | my_fn = elf.get_fn_from_my_elf(fn.decomp_name) 32 | except KeyError: 33 | utils.warn(f"could not find function {fn.decomp_name}") 34 | continue 35 | 36 | assert len(base_fn.data) == len(my_fn.data) 37 | 38 | vtable_ptr1 = 0 39 | vtable_ptr2 = 0 40 | for j, (i1, i2) in enumerate(zip(md.disasm(base_fn.data, base_fn.addr), md.disasm(my_fn.data, my_fn.addr))): 41 | assert i1.mnemonic == i2.mnemonic 42 | if j == 10: 43 | assert i1.mnemonic == "adrp" 44 | assert i1.operands[0].reg == i2.operands[0].reg 45 | vtable_ptr1 = i1.operands[1].imm 46 | vtable_ptr2 = i2.operands[1].imm 47 | elif j == 11: 48 | assert i1.mnemonic == "ldr" 49 | assert i1.operands[0].reg == i2.operands[0].reg 50 | assert i1.operands[1].value.mem.base == i2.operands[1].value.mem.base 51 | vtable_ptr1 += i1.operands[1].value.mem.disp 52 | vtable_ptr2 += i2.operands[1].value.mem.disp 53 | break 54 | 55 | assert vtable_ptr1 != 0 and vtable_ptr2 != 0 56 | if vtable_ptr1 in processed: 57 | continue 58 | processed.add(vtable_ptr1) 59 | ptr1, = struct.unpack("7d} {label}{Fore.RESET} ({percentage}% | size: {size_percentage}%)" 53 | 54 | 55 | def format_progress_for_status(label: str, status: FunctionStatus): 56 | return format_progress(label, counts[status], code_size[status]) 57 | 58 | 59 | if args.csv: 60 | import git 61 | 62 | version = 1 63 | git_object = git.Repo().head.object 64 | timestamp = str(git_object.committed_date) 65 | git_hash = git_object.hexsha 66 | 67 | fields = [ 68 | str(version), 69 | timestamp, 70 | git_hash, 71 | 72 | str(num_total), 73 | str(code_size_total), 74 | 75 | str(counts[FunctionStatus.Matching]), 76 | str(code_size[FunctionStatus.Matching]), 77 | 78 | str(counts[FunctionStatus.Equivalent]), 79 | str(code_size[FunctionStatus.Equivalent]), 80 | 81 | str(counts[FunctionStatus.NonMatching]), 82 | str(code_size[FunctionStatus.NonMatching]), 83 | ] 84 | print(",".join(fields)) 85 | 86 | else: 87 | print() 88 | 89 | print(f"{num_total:>7d} functions (size: ~{code_size_total} bytes)") 90 | 91 | count_decompiled = counts[FunctionStatus.Matching] + counts[FunctionStatus.Equivalent] + counts[ 92 | FunctionStatus.NonMatching] 93 | code_size_decompiled = code_size[FunctionStatus.Matching] + code_size[FunctionStatus.Equivalent] + code_size[ 94 | FunctionStatus.NonMatching] 95 | 96 | print(format_progress(f"{Fore.CYAN}decompiled", count_decompiled, code_size_decompiled)) 97 | print(format_progress_for_status(f"{Fore.GREEN}matching", FunctionStatus.Matching)) 98 | print(format_progress_for_status(f"{Fore.YELLOW}non-matching (minor issues)", FunctionStatus.Equivalent)) 99 | print(format_progress_for_status(f"{Fore.RED}non-matching (major issues)", FunctionStatus.NonMatching)) 100 | print() 101 | -------------------------------------------------------------------------------- /rename_functions_in_ida.py: -------------------------------------------------------------------------------- 1 | # Renames functions in an IDA database to match the function names 2 | # in the decompiled source code. 3 | 4 | import csv 5 | import idc 6 | import os 7 | from util import config 8 | 9 | csv_path = config.get_functions_csv_path() 10 | 11 | def can_overwrite_name(addr: int, new_name: str): 12 | if not new_name or new_name.startswith(("sub_", "nullsub_", "j_")): 13 | return False 14 | 15 | old_name: str = idc.get_name(addr) 16 | # If we don't have an existing name, then the function can always be renamed. 17 | if not old_name: 18 | return True 19 | 20 | # Auto-generated names can be overwritten. 21 | if old_name.startswith(("sub_", "nullsub_", "j_")): 22 | return True 23 | 24 | # If the existing name is mangled, then it probably came from the function list CSV 25 | # so it can be overwritten. 26 | if old_name.startswith("_Z"): 27 | return True 28 | 29 | # Prefer mangled names to temporary names. 30 | if new_name.startswith("_Z"): 31 | return True 32 | 33 | # Otherwise, we return false to avoid losing temporary names. 34 | return False 35 | 36 | 37 | with open(csv_path, "r") as f: 38 | reader = csv.reader(f) 39 | # Skip headers 40 | next(reader) 41 | for fn in reader: 42 | addr = int(fn[0], 16) 43 | name = fn[3] 44 | if can_overwrite_name(addr, name): 45 | idc.set_name(addr, name, idc.SN_CHECK | idc.SN_NOWARN) 46 | -------------------------------------------------------------------------------- /setup_common.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from pathlib import Path 3 | import subprocess 4 | import sys 5 | import tarfile 6 | import tempfile 7 | import urllib.request 8 | 9 | from common.util import config, tools 10 | 11 | ROOT = Path(__file__).parent.parent.parent 12 | 13 | def get_target_path(version = config.get_default_version()): 14 | return config.get_versioned_data_path(version) / "main.nso" 15 | 16 | def get_target_elf_path(version = config.get_default_version()): 17 | return config.get_versioned_data_path(version) / "main.elf" 18 | 19 | 20 | def fail(error: str): 21 | print(">>> " + error) 22 | sys.exit(1) 23 | 24 | def _convert_nso_to_elf(nso_path: Path): 25 | print(">>>> converting NSO to ELF...") 26 | subprocess.check_call([tools.find_tool("nx2elf"), str(nso_path)]) 27 | 28 | 29 | def _decompress_nso(nso_path: Path, dest_path: Path): 30 | print(">>>> decompressing NSO...") 31 | subprocess.check_call([tools.find_tool("hactool"), "-tnso", 32 | "--uncompressed=" + str(dest_path), str(nso_path)]) 33 | 34 | def install_viking(): 35 | print(">>>> installing viking (tools/check)") 36 | src_path = ROOT / "tools" / "common" / "viking" 37 | install_path = ROOT / "tools" 38 | 39 | try: 40 | subprocess.check_call(["cargo", "build", "--manifest-path", src_path / "Cargo.toml", "--release"]) 41 | for tool in ["check", "listsym", "decompme"]: 42 | (src_path / "target" / "release" / tool).rename(install_path / tool) 43 | except FileNotFoundError: 44 | print(sys.exc_info()[0]) 45 | fail("error: install cargo (rust) and try again") 46 | 47 | def _apply_xdelta3_patch(input: Path, patch: Path, dest: Path): 48 | print(">>>> applying patch...") 49 | try: 50 | subprocess.check_call(["xdelta3", "-d", "-s", str(input), str(patch), str(dest)]) 51 | except FileNotFoundError: 52 | fail("error: install xdelta3 and try again") 53 | 54 | def set_up_compiler(version): 55 | compiler_dir = ROOT / "toolchain" / ("clang-"+version) 56 | if compiler_dir.is_dir(): 57 | print(">>> clang is already set up: nothing to do") 58 | return 59 | 60 | system = platform.system() 61 | machine = platform.machine() 62 | 63 | if version == "3.9.1": 64 | builds = { 65 | # Linux 66 | ("Linux", "x86_64"): { 67 | "url": "https://releases.llvm.org/3.9.1/clang+llvm-3.9.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz", 68 | "dir_name": "clang+llvm-3.9.1-x86_64-linux-gnu-ubuntu-16.04", 69 | }, 70 | ("Linux", "aarch64"): { 71 | "url": "https://releases.llvm.org/3.9.1/clang+llvm-3.9.1-aarch64-linux-gnu.tar.xz", 72 | "dir_name": "clang+llvm-3.9.1-aarch64-linux-gnu", 73 | } 74 | } 75 | elif version == "4.0.1": 76 | builds = { 77 | # Linux 78 | ("Linux", "x86_64"): { 79 | "url": "https://releases.llvm.org/4.0.1/clang+llvm-4.0.1-x86_64-linux-gnu-Fedora-25.tar.xz", 80 | "dir_name": "clang+llvm-4.0.1-x86_64-linux-gnu-Fedora-25", 81 | }, 82 | ("Linux", "aarch64"): { 83 | "url": "https://releases.llvm.org/4.0.1/clang+llvm-4.0.1-aarch64-linux-gnu.tar.xz", 84 | "dir_name": "clang+llvm-4.0.1-aarch64-linux-gnu", 85 | }, 86 | 87 | # macOS 88 | ("Darwin", "x86_64"): { 89 | "url": "https://releases.llvm.org/4.0.1/clang+llvm-4.0.1-x86_64-apple-darwin.tar.xz", 90 | "dir_name": "clang+llvm-4.0.1-x86_64-apple-macosx10.9.0", 91 | }, 92 | ("Darwin", "arm64"): { 93 | "url": "https://releases.llvm.org/4.0.1/clang+llvm-4.0.1-x86_64-apple-darwin.tar.xz", 94 | "dir_name": "clang+llvm-4.0.1-x86_64-apple-macosx10.9.0", 95 | }, 96 | } 97 | elif version == "5.0.1": 98 | builds = { 99 | # Linux 100 | ("Linux", "x86_64"): { 101 | "url": "https://releases.llvm.org/5.0.1/clang+llvm-5.0.1-x86_64-linux-gnu-Fedora27.tar.xz", 102 | "dir_name": "clang+llvm-5.0.1-x86_64-linux-gnu-Fedora27", 103 | }, 104 | ("Linux", "aarch64"): { 105 | "url": "https://releases.llvm.org/5.0.1/clang+llvm-5.0.1-aarch64-linux-gnu.tar.xz", 106 | "dir_name": "clang+llvm-5.0.1-aarch64-linux-gnu", 107 | }, 108 | 109 | # macOS 110 | ("Darwin", "x86_64"): { 111 | "url": "https://releases.llvm.org/5.0.1/clang+llvm-5.0.1-x86_64-apple-darwin.tar.xz", 112 | "dir_name": "clang+llvm-5.0.1-x86_64-apple-macosx10.9.0", 113 | }, 114 | ("Darwin", "arm64"): { 115 | "url": "https://releases.llvm.org/5.0.1/clang+llvm-5.0.1-x86_64-apple-darwin.tar.xz", 116 | "dir_name": "clang+llvm-5.0.1-x86_64-apple-macosx10.9.0", 117 | }, 118 | } 119 | elif version == "7.0.0": 120 | builds = { 121 | # Linux 122 | ("Linux", "x86_64"): { 123 | "url": "https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz", 124 | "dir_name": "clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04", 125 | }, 126 | ("Linux", "aarch64"): { 127 | "url": "https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-aarch64-linux-gnu.tar.xz", 128 | "dir_name": "clang+llvm-7.1.0-aarch64-linux-gnu", 129 | }, 130 | 131 | # macOS 132 | ("Darwin", "x86_64"): { 133 | "url": "https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz", 134 | "dir_name": "clang+llvm-7.0.0-x86_64-apple-darwin", 135 | } 136 | } 137 | elif version == "7.1.0": 138 | builds = { 139 | # Linux 140 | ("Linux", "x86_64"): { 141 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/clang+llvm-7.1.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz", 142 | "dir_name": "clang+llvm-7.1.0-x86_64-linux-gnu-ubuntu-14.04", 143 | }, 144 | ("Linux", "aarch64"): { 145 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/clang+llvm-7.1.0-aarch64-linux-gnu.tar.xz", 146 | "dir_name": "clang+llvm-7.1.0-aarch64-linux-gnu", 147 | } 148 | } 149 | elif version == "8.0.0": 150 | builds = { 151 | # Linux 152 | ("Linux", "x86_64"): { 153 | "url": "https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz", 154 | "dir_name": "clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04", 155 | }, 156 | ("Linux", "aarch64"): { 157 | "url": "https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-aarch64-linux-gnu.tar.xz", 158 | "dir_name": "clang+llvm-8.0.0-aarch64-linux-gnu", 159 | }, 160 | 161 | # macOS 162 | ("Darwin", "x86_64"): { 163 | "url": "https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz", 164 | "dir_name": "clang+llvm-8.0.0-x86_64-apple-darwin", 165 | } 166 | } 167 | elif version == "9.0.0": 168 | builds = { 169 | # Linux 170 | ("Linux", "x86_64"): { 171 | "url": "https://releases.llvm.org/9.0.0/clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz", 172 | "dir_name": "clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-18.04", 173 | }, 174 | ("Linux", "aarch64"): { 175 | "url": "https://releases.llvm.org/9.0.0/clang+llvm-9.0.0-aarch64-linux-gnu.tar.xz", 176 | "dir_name": "clang+llvm-9.0.0-aarch64-linux-gnu", 177 | }, 178 | 179 | # macOS 180 | ("Darwin", "x86_64"): { 181 | "url": "https://releases.llvm.org/9.0.0/clang+llvm-9.0.0-x86_64-darwin-apple.tar.xz", 182 | "dir_name": "clang+llvm-9.0.0-x86_64-apple-darwin", 183 | } 184 | } 185 | elif version == "10.0.0": 186 | builds = { 187 | # Linux 188 | ("Linux", "x86_64"): { 189 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz", 190 | "dir_name": "clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04", 191 | }, 192 | ("Linux", "aarch64"): { 193 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-aarch64-linux-gnu.tar.xz", 194 | "dir_name": "clang+llvm-10.0.0-aarch64-linux-gnu", 195 | }, 196 | 197 | # macOS 198 | ("Darwin", "x86_64"): { 199 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-apple-darwin.tar.xz", 200 | "dir_name": "clang+llvm-10.0.0-x86_64-apple-darwin", 201 | } 202 | } 203 | elif version == "11.0.0": 204 | builds = { 205 | # Linux 206 | ("Linux", "x86_64"): { 207 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz", 208 | "dir_name": "clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04", 209 | }, 210 | ("Linux", "aarch64"): { 211 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang+llvm-11.0.0-aarch64-linux-gnu.tar.xz", 212 | "dir_name": "clang+llvm-11.0.0-aarch64-linux-gnu", 213 | }, 214 | 215 | # macOS 216 | ("Darwin", "x86_64"): { 217 | "url": "https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang+llvm-11.0.0-x86_64-apple-darwin.tar.xz", 218 | "dir_name": "clang+llvm-11.0.0-x86_64-apple-darwin", 219 | } 220 | } 221 | else: 222 | builds = {} 223 | 224 | build_info = builds.get((system, machine)) 225 | if build_info is None: 226 | fail( 227 | f"unknown platform: {platform.platform()} - {version} (please report if you are on Linux and macOS)") 228 | 229 | url: str = build_info["url"] 230 | dir_name: str = build_info["dir_name"] 231 | 232 | print(f">>> downloading Clang from {url}...") 233 | with tempfile.TemporaryDirectory() as tmpdir: 234 | path = tmpdir + "/" + url.split("/")[-1] 235 | urllib.request.urlretrieve(url, path) 236 | 237 | print(f">>> extracting Clang...") 238 | with tarfile.open(path) as f: 239 | f.extractall(compiler_dir.parent) 240 | (compiler_dir.parent / dir_name).rename(compiler_dir) 241 | 242 | print(">>> successfully set up Clang") 243 | -------------------------------------------------------------------------------- /show_vtable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import struct 5 | from typing import Optional 6 | 7 | import cxxfilt 8 | from colorama import Fore, Style 9 | 10 | import util.elf 11 | from util import utils 12 | 13 | 14 | def find_vtable(symtab, class_name: str) -> Optional[str]: 15 | name_offset = len("vtable for ") 16 | for sym in util.elf.iter_symbols(symtab): 17 | if not sym.name.startswith("_ZTV"): 18 | continue 19 | if cxxfilt.demangle(sym.name)[name_offset:] == class_name: 20 | return sym.name 21 | return None 22 | 23 | 24 | def bold(s) -> str: 25 | return Style.BRIGHT + str(s) + Style.NORMAL 26 | 27 | 28 | def dump_table(name: str) -> None: 29 | try: 30 | symbols = util.elf.build_addr_to_symbol_table(util.elf.my_symtab) 31 | decomp_symbols = {fn.decomp_name for fn in utils.get_functions() if fn.decomp_name} 32 | 33 | offset, size = util.elf.get_symbol_file_offset_and_size(util.elf.my_elf, util.elf.my_symtab, name) 34 | util.elf.my_elf.stream.seek(offset) 35 | vtable_bytes = util.elf.my_elf.stream.read(size) 36 | 37 | if not vtable_bytes: 38 | utils.fail( 39 | "empty vtable; has the key function been implemented? (https://lld.llvm.org/missingkeyfunction.html)") 40 | 41 | print(f"{Fore.WHITE}{Style.BRIGHT}{cxxfilt.demangle(name)}{Style.RESET_ALL}") 42 | print(f"{Fore.YELLOW}{Style.BRIGHT}vtable @ 0x0{Style.RESET_ALL}") 43 | 44 | assert size % 8 == 0 45 | for i in range(size // 8): 46 | word: int = struct.unpack_from(" None: 67 | parser = argparse.ArgumentParser() 68 | parser.add_argument("symbol_name", help="Name of the vtable symbol (_ZTV...) or class name") 69 | args = parser.parse_args() 70 | 71 | symbol_name: str = args.symbol_name 72 | 73 | if not symbol_name.startswith("_ZTV"): 74 | symbol_name = find_vtable(util.elf.my_symtab, args.symbol_name) 75 | 76 | dump_table(symbol_name) 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /translate_ida_types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from colorama import Back, Fore, Style 4 | import sys 5 | 6 | mapping = { 7 | "agl::utl::Parameter$uint$": "agl::utl::Parameter", 8 | "agl::utl::Parameter$int$": "agl::utl::Parameter", 9 | "agl::utl::Parameter$s32$": "agl::utl::Parameter", 10 | "agl::utl::Parameter$float$": "agl::utl::Parameter", 11 | "agl::utl::Parameter$f32$": "agl::utl::Parameter", 12 | "agl::utl::Parameter$bool$": "agl::utl::Parameter", 13 | "agl::utl::Parameter$sead::SafeString$": "agl::utl::Parameter", 14 | "agl::utl::Parameter$sead::Vector3f$": "agl::utl::Parameter", 15 | "agl::utl::Parameter$sead::FixedSafeString20$": "agl::utl::Parameter>", 16 | "agl::utl::Parameter$sead::FixedSafeString40$": "agl::utl::Parameter>", 17 | "agl::utl::Parameter$sead::FixedSafeString100$": "agl::utl::Parameter>", 18 | "agl::utl::Parameter$sead::Color4f$": "agl::utl::Parameter", 19 | "agl::utl::Parameter_String32": "agl::utl::Parameter>", 20 | "agl::utl::Parameter_String64": "agl::utl::Parameter>", 21 | "agl::utl::Parameter_String256": "agl::utl::Parameter>", 22 | } 23 | 24 | lines = list(sys.stdin) 25 | 26 | sys.stderr.write(Back.BLUE + Fore.WHITE + Style.BRIGHT + "=" * 30 + " output " + "=" * 30 + Style.RESET_ALL + "\n") 27 | 28 | for line in lines: 29 | for from_type, to_type in mapping.items(): 30 | line = line.replace(from_type, to_type) 31 | sys.stdout.write(line) 32 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ead/nx-decomp-tools/862a2c5c280076db472cf5f7a411ed83f522dd6b/util/__init__.py -------------------------------------------------------------------------------- /util/checker.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from collections import defaultdict 3 | from typing import Set, DefaultDict, Dict, Optional, Tuple 4 | from pathlib import Path 5 | 6 | import capstone as cs 7 | 8 | from . import dsym, elf, utils, config 9 | 10 | _store_instructions = ("str", "strb", "strh", "stur", "sturb", "sturh") 11 | 12 | 13 | class FunctionChecker: 14 | def __init__(self, log_mismatch_cause: bool = False): 15 | self.md = cs.Cs(cs.CS_ARCH_ARM64, cs.CS_MODE_ARM) 16 | self.md.detail = True 17 | self.my_symtab = elf.build_name_to_symbol_table(elf.my_symtab) 18 | self.dsymtab = dsym.DataSymbolContainer() 19 | self.decompiled_fns: Dict[int, str] = dict() 20 | 21 | self._log_mismatch_cause = log_mismatch_cause 22 | self._mismatch_addr1 = -1 23 | self._mismatch_addr2 = -1 24 | self._mismatch_cause = "" 25 | self._base_got_section = elf.base_elf.get_section_by_name(".got") 26 | self._decomp_glob_data_table = elf.build_glob_data_table(elf.my_elf) 27 | self._got_data_symbol_check_cache: Dict[Tuple[int, int], bool] = dict() 28 | 29 | self.load_data_for_project() 30 | 31 | def _reset_mismatch(self) -> None: 32 | self._mismatch_addr1 = -1 33 | self._mismatch_addr2 = -1 34 | self._mismatch_cause = "" 35 | 36 | def get_data_symtab(self) -> dsym.DataSymbolContainer: 37 | return self.dsymtab 38 | 39 | def get_mismatch(self) -> (int, int, str): 40 | return self._mismatch_addr1, self._mismatch_addr2, self._mismatch_cause 41 | 42 | def get_data_symbols_path(self, version = config.get_default_version()) -> Path: 43 | return config.get_versioned_data_path(version) / "data_symbols.csv" 44 | 45 | def load_data_for_project(self) -> None: 46 | self.decompiled_fns = {func.addr: func.decomp_name for func in utils.get_functions() if func.decomp_name} 47 | self.get_data_symtab().load_from_csv(self.get_data_symbols_path()) 48 | 49 | def check(self, base_fn: elf.Function, my_fn: elf.Function) -> bool: 50 | self._reset_mismatch() 51 | gprs1: DefaultDict[int, int] = defaultdict(int) 52 | gprs2: DefaultDict[int, int] = defaultdict(int) 53 | adrp_pair_registers: Set[int] = set() 54 | 55 | size = len(base_fn) 56 | if len(base_fn) != len(my_fn): 57 | if self._log_mismatch_cause: 58 | self._set_mismatch_cause(None, None, "different function length") 59 | return False 60 | 61 | def forget_modified_registers(insn): 62 | _, regs_write = insn.regs_access() 63 | for reg in regs_write: 64 | adrp_pair_registers.discard(reg) 65 | 66 | for i1, i2 in zip(self.md.disasm(base_fn.data, base_fn.addr), self.md.disasm(my_fn.data, my_fn.addr)): 67 | if i1.bytes == i2.bytes: 68 | if i1.mnemonic == 'adrp': 69 | gprs1[i1.operands[0].reg] = i1.operands[1].imm 70 | gprs2[i2.operands[0].reg] = i2.operands[1].imm 71 | adrp_pair_registers.add(i1.operands[0].reg) 72 | elif i1.mnemonic == 'b': 73 | branch_target = i1.operands[0].imm 74 | if not (base_fn.addr <= branch_target < base_fn.addr + size): 75 | if not self._check_function_call(i1, i2, branch_target, i2.operands[0].imm): 76 | return False 77 | else: 78 | forget_modified_registers(i1) 79 | continue 80 | 81 | if i1.mnemonic != i2.mnemonic: 82 | if self._log_mismatch_cause: 83 | self._set_mismatch_cause(i1, i2, "mnemonics are different") 84 | return False 85 | 86 | # Ignore some address differences until a fully matching executable can be generated. 87 | 88 | if i1.mnemonic == 'bl': 89 | if not self._check_function_call(i1, i2, i1.operands[0].imm, i2.operands[0].imm): 90 | return False 91 | continue 92 | 93 | if i1.mnemonic == 'b': 94 | branch_target = i1.operands[0].imm 95 | # If we are branching outside the function, this is likely a tail call. 96 | # Treat this as a function call. 97 | if not (base_fn.addr <= branch_target < base_fn.addr + size): 98 | if not self._check_function_call(i1, i2, branch_target, i2.operands[0].imm): 99 | return False 100 | continue 101 | # Otherwise, it's a mismatch. 102 | return False 103 | 104 | if i1.mnemonic == 'adrp': 105 | if i1.operands[0].reg != i2.operands[0].reg: 106 | return False 107 | reg = i1.operands[0].reg 108 | 109 | gprs1[reg] = i1.operands[1].imm 110 | gprs2[reg] = i2.operands[1].imm 111 | 112 | adrp_pair_registers.add(reg) 113 | continue 114 | 115 | if i1.mnemonic == 'ldp' or i1.mnemonic == 'ldpsw' or i1.mnemonic == 'stp': 116 | if i1.operands[0].reg != i2.operands[0].reg: 117 | return False 118 | if i1.operands[1].reg != i2.operands[1].reg: 119 | return False 120 | if i1.operands[2].value.mem.base != i2.operands[2].value.mem.base: 121 | return False 122 | reg = i1.operands[2].value.mem.base 123 | if reg not in adrp_pair_registers: 124 | return False 125 | 126 | gprs1[reg] += i1.operands[2].value.mem.disp 127 | gprs2[reg] += i2.operands[2].value.mem.disp 128 | if not self._check_data_symbol_load(i1, i2, gprs1[reg], gprs2[reg]): 129 | return False 130 | 131 | forget_modified_registers(i1) 132 | continue 133 | 134 | if i1.mnemonic.startswith('ld') or i1.mnemonic in _store_instructions: 135 | if i1.operands[0].reg != i2.operands[0].reg: 136 | return False 137 | if i1.operands[1].value.mem.base != i2.operands[1].value.mem.base: 138 | return False 139 | reg = i1.operands[1].value.mem.base 140 | if reg not in adrp_pair_registers: 141 | return False 142 | 143 | gprs1[reg] += i1.operands[1].value.mem.disp 144 | gprs2[reg] += i2.operands[1].value.mem.disp 145 | if not self._check_data_symbol_load(i1, i2, gprs1[reg], gprs2[reg]): 146 | return False 147 | 148 | forget_modified_registers(i1) 149 | continue 150 | 151 | if i1.mnemonic == 'add': 152 | if i1.operands[0].reg != i2.operands[0].reg: 153 | return False 154 | if i1.operands[1].reg != i2.operands[1].reg: 155 | return False 156 | reg = i1.operands[1].reg 157 | if reg not in adrp_pair_registers: 158 | return False 159 | 160 | gprs1[reg] += i1.operands[2].imm 161 | gprs2[reg] += i2.operands[2].imm 162 | if not self._check_data_symbol(i1, i2, gprs1[reg], gprs2[reg]): 163 | return False 164 | 165 | forget_modified_registers(i1) 166 | continue 167 | 168 | return False 169 | 170 | return True 171 | 172 | def _set_mismatch_cause(self, i1: Optional[any], i2: Optional[any], description: str) -> None: 173 | self._mismatch_addr1 = i1.address if i1 else -1 174 | self._mismatch_addr2 = i2.address if i2 else -1 175 | self._mismatch_cause = description 176 | 177 | def _check_data_symbol(self, i1, i2, orig_addr: int, decomp_addr: int) -> bool: 178 | symbol = self.dsymtab.get_symbol(orig_addr) 179 | if symbol is None: 180 | return True 181 | 182 | decomp_symbol = self.my_symtab[symbol.name] 183 | if decomp_symbol.addr == decomp_addr: 184 | return True 185 | 186 | if self._log_mismatch_cause: 187 | self._set_mismatch_cause(i1, i2, f"data symbol mismatch: {symbol.name} (original address: {orig_addr:#x}, " 188 | f"expected: {decomp_symbol.addr:#x}, " 189 | f"actual: {decomp_addr:#x})") 190 | 191 | return False 192 | 193 | def _check_data_symbol_load(self, i1, i2, orig_addr: int, decomp_addr: int) -> bool: 194 | cached_result = self._got_data_symbol_check_cache.get((orig_addr, decomp_addr), None) 195 | if cached_result is not None: 196 | return cached_result 197 | 198 | if not elf.is_in_section(self._base_got_section, orig_addr, 8): 199 | return True 200 | 201 | ptr1, = struct.unpack(" bool: 212 | name = self.decompiled_fns.get(orig_addr, None) 213 | if name is None: 214 | self.on_unknown_fn_call(orig_addr, decomp_addr) 215 | return True 216 | 217 | decomp_symbol = self.my_symtab[name] 218 | if decomp_symbol.addr == decomp_addr: 219 | return True 220 | 221 | if self._log_mismatch_cause: 222 | self._set_mismatch_cause(i1, i2, f"function call mismatch: {name}") 223 | 224 | return False 225 | 226 | def on_unknown_fn_call(self, orig_addr: int, decomp_addr: int) -> None: 227 | pass 228 | -------------------------------------------------------------------------------- /util/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import toml 3 | 4 | 5 | def get_repo_root() -> Path: 6 | return Path(__file__).resolve().parent.parent.parent.parent 7 | 8 | CONFIG = toml.load(get_repo_root() / "tools" / "config.toml") 9 | 10 | def get_default_version() -> str: 11 | return CONFIG.get("default_version") 12 | 13 | def get_versioned_data_path(version = get_default_version()) -> Path: 14 | value = get_repo_root() / 'data' 15 | 16 | if version is not None: 17 | value /= version 18 | 19 | return value 20 | 21 | def get_functions_csv_path(version = None) -> Path: 22 | value = CONFIG["functions_csv"] 23 | if version is None: 24 | version = get_default_version() 25 | 26 | if version is not None: 27 | value = value.replace("{version}", version) 28 | 29 | if "{version}" in value: 30 | raise RuntimeError("You should probably pass a --version parameter. If this error still shows up with the argument given, please contact the repo maintainers.") 31 | 32 | return get_repo_root() / value 33 | 34 | def get_base_elf(version = get_default_version()) -> Path: 35 | return get_versioned_data_path() / 'main.elf' 36 | 37 | def get_build_target() -> str: 38 | return CONFIG["build_target"] 39 | 40 | def get_decomp_elf(version = get_default_version()) -> Path: 41 | value = get_repo_root() / 'build' 42 | 43 | if version is not None: 44 | value /= version 45 | 46 | return value / get_build_target() 47 | -------------------------------------------------------------------------------- /util/dsym.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from pathlib import Path 3 | import typing as tp 4 | 5 | from . import elf 6 | 7 | 8 | class DataSymbol(tp.NamedTuple): 9 | addr: int # without the 0x7100000000 base 10 | name: str 11 | size: int 12 | 13 | 14 | _IDA_BASE = 0x7100000000 15 | 16 | 17 | class DataSymbolContainer: 18 | def __init__(self) -> None: 19 | self.symbols: tp.List[DataSymbol] = [] 20 | 21 | def load_from_csv(self, path: Path): 22 | symtab = elf.build_name_to_symbol_table(elf.my_symtab) 23 | 24 | with path.open("r") as f: 25 | for i, line in enumerate(csv.reader(f)): 26 | if len(line) != 2: 27 | raise RuntimeError(f"Invalid line format at line {i}") 28 | 29 | addr = int(line[0], 16) - _IDA_BASE 30 | name = line[1] 31 | if name not in symtab: 32 | continue 33 | size = symtab[name].size 34 | 35 | self.symbols.append(DataSymbol(addr, name, size)) 36 | 37 | # Sort the list, just in case the entries were not sorted in the CSV. 38 | self.symbols.sort(key=lambda sym: sym.addr) 39 | 40 | def get_symbol(self, addr: int) -> tp.Optional[DataSymbol]: 41 | """If addr is part of a known data symbol, this function returns the corresponding symbol.""" 42 | 43 | # Perform a binary search on self.symbols. 44 | a = 0 45 | b = len(self.symbols) - 1 46 | while a <= b: 47 | m = (a + b) // 2 48 | 49 | symbol: DataSymbol = self.symbols[m] 50 | addr_begin = symbol.addr 51 | addr_end = addr_begin + symbol.size 52 | 53 | if addr_begin <= addr < addr_end: 54 | return symbol 55 | if addr <= addr_begin: 56 | b = m - 1 57 | elif addr >= addr_end: 58 | a = m + 1 59 | else: 60 | return None 61 | 62 | return None 63 | -------------------------------------------------------------------------------- /util/elf.py: -------------------------------------------------------------------------------- 1 | import io 2 | import struct 3 | from typing import Any, Dict, NamedTuple, Tuple 4 | 5 | from elftools.elf.elffile import ELFFile 6 | from elftools.elf.relocation import RelocationSection 7 | from elftools.elf.sections import Section 8 | 9 | from . import utils, config 10 | 11 | base_elf_data = io.BytesIO(config.get_base_elf().read_bytes()) 12 | my_elf_data = io.BytesIO(config.get_decomp_elf().read_bytes()) 13 | 14 | base_elf = ELFFile(base_elf_data) 15 | my_elf = ELFFile(my_elf_data) 16 | my_symtab = my_elf.get_section_by_name(".symtab") 17 | if not my_symtab: 18 | utils.fail(f'{config.get_decomp_elf()} has no symbol table') 19 | 20 | 21 | class Symbol(NamedTuple): 22 | addr: int 23 | name: str 24 | size: int 25 | 26 | 27 | class Function(NamedTuple): 28 | data: bytes 29 | addr: int 30 | 31 | 32 | _ElfSymFormat = struct.Struct(" int: 49 | for seg in elf.iter_segments(): 50 | if seg.header["p_type"] != "PT_LOAD": 51 | continue 52 | if seg["p_vaddr"] <= addr < seg["p_vaddr"] + seg["p_filesz"]: 53 | return addr - seg["p_vaddr"] + seg["p_offset"] 54 | raise KeyError(f"No segment found for {addr:#x}") 55 | 56 | 57 | def is_in_section(section: Section, addr: int, size: int) -> bool: 58 | begin = section["sh_addr"] 59 | end = begin + section["sh_size"] 60 | return begin <= addr < end and begin <= addr + size < end 61 | 62 | 63 | _TableCache = dict() 64 | 65 | 66 | def make_table_cached(symtab): 67 | table = _TableCache.get(id(symtab)) 68 | if table is None: 69 | table = build_name_to_symbol_table(symtab) 70 | _TableCache[id(symtab)] = table 71 | return table 72 | 73 | 74 | def get_symbol(symtab, name: str) -> Symbol: 75 | table = make_table_cached(symtab) 76 | return table[name] 77 | 78 | 79 | def get_symbol_file_offset_and_size(elf, table, name: str) -> (int, int): 80 | sym = get_symbol(table, name) 81 | return get_file_offset(elf, sym.addr), sym.size 82 | 83 | 84 | def iter_symbols(symtab): 85 | offset = symtab["sh_offset"] 86 | entsize = symtab["sh_entsize"] 87 | for i in range(symtab.num_symbols()): 88 | symtab.stream.seek(offset + i * entsize) 89 | entry = _ElfSym.parse(symtab.stream.read(_ElfSymFormat.size)) 90 | name = symtab.stringtable.get_string(entry.st_name) 91 | yield Symbol(entry.st_value, name, entry.st_size) 92 | 93 | 94 | def build_addr_to_symbol_table(symtab) -> Dict[int, str]: 95 | table = dict() 96 | for sym in iter_symbols(symtab): 97 | addr = sym.addr 98 | existing_value = table.get(addr, None) 99 | if existing_value is None or not existing_value.startswith("_Z"): 100 | table[addr] = sym.name 101 | return table 102 | 103 | 104 | def build_name_to_symbol_table(symtab) -> Dict[str, Symbol]: 105 | return {sym.name: sym for sym in iter_symbols(symtab)} 106 | 107 | 108 | def read_from_elf(elf: ELFFile, addr: int, size: int) -> bytes: 109 | addr &= ~0x7100000000 110 | offset: int = get_file_offset(elf, addr) 111 | elf.stream.seek(offset) 112 | return elf.stream.read(size) 113 | 114 | 115 | def get_fn_from_base_elf(addr: int, size: int) -> Function: 116 | return Function(read_from_elf(base_elf, addr, size), addr) 117 | 118 | 119 | def get_fn_from_my_elf(name: str) -> Function: 120 | sym = get_symbol(my_symtab, name) 121 | return Function(read_from_elf(my_elf, sym.addr, sym.size), sym.addr) 122 | 123 | 124 | R_AARCH64_GLOB_DAT = 1025 125 | R_AARCH64_RELATIVE = 1027 126 | 127 | 128 | def build_glob_data_table(elf: ELFFile) -> Dict[int, int]: 129 | table: Dict[int, int] = dict() 130 | section = elf.get_section_by_name(".rela.dyn") 131 | assert isinstance(section, RelocationSection) 132 | 133 | symtab = elf.get_section(section["sh_link"]) 134 | offset = symtab["sh_offset"] 135 | entsize = symtab["sh_entsize"] 136 | 137 | for reloc in section.iter_relocations(): 138 | symtab.stream.seek(offset + reloc["r_info_sym"] * entsize) 139 | sym_value = _ElfSym.parse(symtab.stream.read(_ElfSymFormat.size)).st_value 140 | info_type = reloc["r_info_type"] 141 | if info_type == R_AARCH64_GLOB_DAT: 142 | table[reloc["r_offset"]] = sym_value + reloc["r_addend"] 143 | elif info_type == R_AARCH64_RELATIVE: 144 | # FIXME: this should be Delta(S) + A 145 | table[reloc["r_offset"]] = sym_value + reloc["r_addend"] 146 | 147 | return table 148 | 149 | 150 | def unpack_vtable_fns(vtable_bytes: bytes, num_entries: int) -> Tuple[int, ...]: 151 | return struct.unpack(f"<{num_entries}Q", vtable_bytes[:num_entries * 8]) 152 | 153 | 154 | def get_vtable_fns_from_base_elf(vtable_addr: int, num_entries: int) -> Tuple[int, ...]: 155 | vtable_bytes = read_from_elf(base_elf, vtable_addr, num_entries * 8) 156 | return unpack_vtable_fns(vtable_bytes, num_entries) 157 | 158 | 159 | def get_vtable_fns_from_my_elf(vtable_name: str, num_entries: int) -> Tuple[int, ...]: 160 | offset, size = get_symbol_file_offset_and_size(my_elf, my_symtab, vtable_name) 161 | my_elf.stream.seek(offset + 0x10) 162 | vtable_bytes = my_elf.stream.read(size - 0x10) 163 | return unpack_vtable_fns(vtable_bytes, num_entries) 164 | -------------------------------------------------------------------------------- /util/graph.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | _Visiting = 0 4 | _Visited = 1 5 | 6 | 7 | class Graph: 8 | def __init__(self): 9 | self.nodes = defaultdict(set) 10 | 11 | def add_edge(self, a, b): 12 | self.nodes[a].add(b) 13 | 14 | def find_connected_components(self): 15 | nodes = defaultdict(list) 16 | for u in self.nodes: 17 | for v in self.nodes[u]: 18 | nodes[u].append(v) 19 | nodes[v].append(u) 20 | cc = [] 21 | visited = set() 22 | 23 | def dfs(start): 24 | result = [] 25 | to_visit = [start] 26 | while to_visit: 27 | x = to_visit.pop() 28 | result.append(x) 29 | visited.add(x) 30 | for y in nodes[x]: 31 | if y not in visited: 32 | to_visit.append(y) 33 | return result 34 | 35 | for u in nodes.keys(): 36 | if u in visited: 37 | continue 38 | cc.append(dfs(u)) 39 | return cc 40 | 41 | def topological_sort(self) -> list: 42 | result = [] 43 | statuses = dict() 44 | 45 | def dfs(node): 46 | if statuses.get(node) == _Visiting: 47 | raise RuntimeError("Graph is not acyclic") 48 | if statuses.get(node) == _Visited: 49 | return 50 | 51 | statuses[node] = _Visiting 52 | for y in self.nodes.get(node, set()): 53 | dfs(y) 54 | 55 | statuses[node] = _Visited 56 | result.insert(0, node) 57 | 58 | for x in self.nodes: 59 | dfs(x) 60 | 61 | return result 62 | -------------------------------------------------------------------------------- /util/tools.py: -------------------------------------------------------------------------------- 1 | from . config import get_repo_root 2 | import platform 3 | import os 4 | 5 | def get_tools_bin_dir(): 6 | path = get_repo_root() / 'tools' / 'common' / 'nx-decomp-tools-binaries' 7 | system = platform.system() 8 | if system == "Linux": 9 | return str(path) + "/linux/" 10 | if system == "Darwin": 11 | return str(path) + "/macos/" 12 | return "" 13 | 14 | def try_find_external_tool(tool: str): 15 | return os.environ.get("NX_DECOMP_TOOLS_%s" % tool.upper().replace("-", "_")) 16 | 17 | def find_tool(tool: str): 18 | tool_from_env = try_find_external_tool(tool) 19 | 20 | if tool_from_env is None: 21 | return get_tools_bin_dir() + tool 22 | 23 | return tool_from_env 24 | -------------------------------------------------------------------------------- /util/utils.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from colorama import Fore, Style 4 | import csv 5 | import warnings 6 | import enum 7 | from pathlib import Path 8 | import sys 9 | import typing as tp 10 | 11 | from . import config 12 | 13 | try: 14 | import cxxfilt 15 | except: 16 | # cxxfilt cannot be used on Windows. 17 | warnings.warn("cxxfilt could not be imported; demangling functions will fail") 18 | 19 | 20 | class FunctionStatus(enum.Enum): 21 | Matching = 0 22 | Equivalent = 1 # semantically equivalent but not perfectly matching 23 | NonMatching = 2 24 | Wip = 3 25 | NotDecompiled = 4 26 | 27 | 28 | class FunctionInfo(tp.NamedTuple): 29 | addr: int # without the 0x7100000000 base 30 | name: str 31 | size: int 32 | decomp_name: str 33 | library: bool 34 | status: FunctionStatus 35 | raw_row: tp.List[str] 36 | 37 | 38 | _markers = { 39 | "O": FunctionStatus.Matching, 40 | "m": FunctionStatus.Equivalent, 41 | "M": FunctionStatus.NonMatching, 42 | "W": FunctionStatus.Wip, 43 | "U": FunctionStatus.NotDecompiled, 44 | "L": FunctionStatus.NotDecompiled, 45 | } 46 | 47 | 48 | def parse_function_csv_entry(row) -> FunctionInfo: 49 | ea, stat, size, name = row 50 | status = _markers.get(stat, FunctionStatus.NotDecompiled) 51 | decomp_name = "" 52 | 53 | if status != FunctionStatus.NotDecompiled: 54 | decomp_name = name 55 | 56 | addr = int(ea, 16) - 0x7100000000 57 | return FunctionInfo(addr, name, int(size), decomp_name, stat == "L", status, row) 58 | 59 | 60 | def get_functions_csv_path(version = None) -> Path: 61 | return config.get_functions_csv_path(version) 62 | 63 | 64 | def get_functions(path: tp.Optional[Path] = None, version = None, all=False) -> tp.Iterable[FunctionInfo]: 65 | if path is None: 66 | path = get_functions_csv_path(version) 67 | with path.open() as f: 68 | reader = csv.reader(f) 69 | # Skip headers 70 | next(reader) 71 | for row in reader: 72 | try: 73 | entry = parse_function_csv_entry(row) 74 | # excluded library function 75 | if entry.library and not all: 76 | continue 77 | yield entry 78 | except ValueError as e: 79 | raise Exception(f"Failed to parse line {reader.line_num}") from e 80 | 81 | 82 | def add_decompiled_functions(new_matches: tp.Dict[int, str], 83 | new_orig_names: tp.Optional[tp.Dict[int, str]] = None) -> None: 84 | buffer = io.StringIO() 85 | writer = csv.writer(buffer, lineterminator="\n") 86 | for func in get_functions(): 87 | if new_orig_names is not None and func.status == FunctionStatus.NotDecompiled and func.addr in new_orig_names: 88 | func.raw_row[3] = new_orig_names[func.addr] 89 | if func.status == FunctionStatus.NotDecompiled and func.addr in new_matches: 90 | func.raw_row[3] = new_matches[func.addr] 91 | writer.writerow(func.raw_row) 92 | get_functions_csv_path().write_text(buffer.getvalue()) 93 | 94 | 95 | def format_symbol_name(name: str) -> str: 96 | try: 97 | return f"{cxxfilt.demangle(name)} {Style.DIM}({name}){Style.RESET_ALL}" 98 | except: 99 | return name 100 | 101 | 102 | def format_symbol_name_for_msg(name: str) -> str: 103 | try: 104 | return f"{Fore.BLUE}{cxxfilt.demangle(name)}{Fore.RESET} {Style.DIM}({name}){Style.RESET_ALL}{Style.BRIGHT}" 105 | except: 106 | return name 107 | 108 | 109 | def are_demangled_names_equal(name1: str, name2: str): 110 | return cxxfilt.demangle(name1) == cxxfilt.demangle(name2) 111 | 112 | 113 | def print_note(msg: str, prefix: str = ""): 114 | sys.stderr.write(f"{Style.BRIGHT}{prefix}{Fore.CYAN}note:{Fore.RESET} {msg}{Style.RESET_ALL}\n") 115 | 116 | 117 | def warn(msg: str, prefix: str = ""): 118 | sys.stderr.write(f"{Style.BRIGHT}{prefix}{Fore.MAGENTA}warning:{Fore.RESET} {msg}{Style.RESET_ALL}\n") 119 | 120 | 121 | def print_error(msg: str, prefix: str = ""): 122 | sys.stderr.write(f"{Style.BRIGHT}{prefix}{Fore.RED}error:{Fore.RESET} {msg}{Style.RESET_ALL}\n") 123 | 124 | 125 | def fail(msg: str, prefix: str = ""): 126 | print_error(msg, prefix) 127 | sys.exit(1) 128 | 129 | 130 | def get_repo_root() -> Path: 131 | return Path(__file__).parent.parent.parent.parent 132 | -------------------------------------------------------------------------------- /viking/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /viking/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.19.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" 10 | dependencies = [ 11 | "cpp_demangle", 12 | "fallible-iterator", 13 | "gimli 0.27.3", 14 | "object 0.30.4", 15 | "rustc-demangle", 16 | "smallvec", 17 | ] 18 | 19 | [[package]] 20 | name = "addr2line" 21 | version = "0.21.0" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 24 | dependencies = [ 25 | "gimli 0.28.1", 26 | ] 27 | 28 | [[package]] 29 | name = "adler" 30 | version = "1.0.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 33 | 34 | [[package]] 35 | name = "aho-corasick" 36 | version = "1.1.3" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 39 | dependencies = [ 40 | "memchr", 41 | ] 42 | 43 | [[package]] 44 | name = "anyhow" 45 | version = "1.0.82" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" 48 | 49 | [[package]] 50 | name = "argh" 51 | version = "0.1.12" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" 54 | dependencies = [ 55 | "argh_derive", 56 | "argh_shared", 57 | ] 58 | 59 | [[package]] 60 | name = "argh_derive" 61 | version = "0.1.12" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" 64 | dependencies = [ 65 | "argh_shared", 66 | "proc-macro2", 67 | "quote", 68 | "syn", 69 | ] 70 | 71 | [[package]] 72 | name = "argh_shared" 73 | version = "0.1.12" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" 76 | dependencies = [ 77 | "serde", 78 | ] 79 | 80 | [[package]] 81 | name = "autocfg" 82 | version = "1.2.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 85 | 86 | [[package]] 87 | name = "backtrace" 88 | version = "0.3.71" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 91 | dependencies = [ 92 | "addr2line 0.21.0", 93 | "cc", 94 | "cfg-if", 95 | "libc", 96 | "miniz_oxide", 97 | "object 0.32.2", 98 | "rustc-demangle", 99 | ] 100 | 101 | [[package]] 102 | name = "bad64" 103 | version = "0.9.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "1ba05edc6ff60c0cacfb48eab9d0f9f8a51663071b47c9673c2340c87ebf12c3" 106 | dependencies = [ 107 | "bad64-sys", 108 | "cstr_core", 109 | "num-derive", 110 | "num-traits", 111 | "static_assertions", 112 | ] 113 | 114 | [[package]] 115 | name = "bad64-sys" 116 | version = "0.7.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "565d858ca3f61068d61bf37020549bbd0692b11ff072352183a9f21226fe78c7" 119 | dependencies = [ 120 | "bindgen", 121 | "cc", 122 | "glob", 123 | ] 124 | 125 | [[package]] 126 | name = "base64" 127 | version = "0.22.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" 130 | 131 | [[package]] 132 | name = "bindgen" 133 | version = "0.69.4" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" 136 | dependencies = [ 137 | "bitflags 2.5.0", 138 | "cexpr", 139 | "clang-sys", 140 | "itertools", 141 | "lazy_static", 142 | "lazycell", 143 | "proc-macro2", 144 | "quote", 145 | "regex", 146 | "rustc-hash", 147 | "shlex", 148 | "syn", 149 | ] 150 | 151 | [[package]] 152 | name = "bitflags" 153 | version = "1.3.2" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 156 | 157 | [[package]] 158 | name = "bitflags" 159 | version = "2.5.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 162 | 163 | [[package]] 164 | name = "bumpalo" 165 | version = "3.16.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 168 | 169 | [[package]] 170 | name = "byteorder" 171 | version = "1.5.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 174 | 175 | [[package]] 176 | name = "bytes" 177 | version = "1.6.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 180 | 181 | [[package]] 182 | name = "capstone" 183 | version = "0.11.0" 184 | source = "git+https://github.com/leoetlino/capstone-rs?rev=f5aa278e1982bca86a67ac8b8550ab1dd70f7d9d#f5aa278e1982bca86a67ac8b8550ab1dd70f7d9d" 185 | dependencies = [ 186 | "capstone-sys", 187 | "libc", 188 | ] 189 | 190 | [[package]] 191 | name = "capstone-sys" 192 | version = "0.15.0" 193 | source = "git+https://github.com/leoetlino/capstone-rs?rev=f5aa278e1982bca86a67ac8b8550ab1dd70f7d9d#f5aa278e1982bca86a67ac8b8550ab1dd70f7d9d" 194 | dependencies = [ 195 | "cc", 196 | "libc", 197 | ] 198 | 199 | [[package]] 200 | name = "cc" 201 | version = "1.0.95" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" 204 | dependencies = [ 205 | "jobserver", 206 | "libc", 207 | "once_cell", 208 | ] 209 | 210 | [[package]] 211 | name = "cexpr" 212 | version = "0.6.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 215 | dependencies = [ 216 | "nom", 217 | ] 218 | 219 | [[package]] 220 | name = "cfg-if" 221 | version = "1.0.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 224 | 225 | [[package]] 226 | name = "cfg_aliases" 227 | version = "0.1.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 230 | 231 | [[package]] 232 | name = "clang-sys" 233 | version = "1.7.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" 236 | dependencies = [ 237 | "glob", 238 | "libc", 239 | "libloading", 240 | ] 241 | 242 | [[package]] 243 | name = "colored" 244 | version = "2.1.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 247 | dependencies = [ 248 | "lazy_static", 249 | "windows-sys 0.48.0", 250 | ] 251 | 252 | [[package]] 253 | name = "core-foundation" 254 | version = "0.9.4" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 257 | dependencies = [ 258 | "core-foundation-sys", 259 | "libc", 260 | ] 261 | 262 | [[package]] 263 | name = "core-foundation-sys" 264 | version = "0.8.6" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 267 | 268 | [[package]] 269 | name = "cpp_demangle" 270 | version = "0.4.3" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" 273 | dependencies = [ 274 | "cfg-if", 275 | ] 276 | 277 | [[package]] 278 | name = "crc32fast" 279 | version = "1.4.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" 282 | dependencies = [ 283 | "cfg-if", 284 | ] 285 | 286 | [[package]] 287 | name = "crossbeam-deque" 288 | version = "0.8.5" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 291 | dependencies = [ 292 | "crossbeam-epoch", 293 | "crossbeam-utils", 294 | ] 295 | 296 | [[package]] 297 | name = "crossbeam-epoch" 298 | version = "0.9.18" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 301 | dependencies = [ 302 | "crossbeam-utils", 303 | ] 304 | 305 | [[package]] 306 | name = "crossbeam-utils" 307 | version = "0.8.19" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 310 | 311 | [[package]] 312 | name = "crossterm" 313 | version = "0.25.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 316 | dependencies = [ 317 | "bitflags 1.3.2", 318 | "crossterm_winapi", 319 | "libc", 320 | "mio", 321 | "parking_lot", 322 | "signal-hook", 323 | "signal-hook-mio", 324 | "winapi", 325 | ] 326 | 327 | [[package]] 328 | name = "crossterm" 329 | version = "0.27.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" 332 | dependencies = [ 333 | "bitflags 2.5.0", 334 | "crossterm_winapi", 335 | "libc", 336 | "mio", 337 | "parking_lot", 338 | "signal-hook", 339 | "signal-hook-mio", 340 | "winapi", 341 | ] 342 | 343 | [[package]] 344 | name = "crossterm_winapi" 345 | version = "0.9.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 348 | dependencies = [ 349 | "winapi", 350 | ] 351 | 352 | [[package]] 353 | name = "cstr_core" 354 | version = "0.2.6" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "dd98742e4fdca832d40cab219dc2e3048de17d873248f83f17df47c1bea70956" 357 | dependencies = [ 358 | "cty", 359 | "memchr", 360 | ] 361 | 362 | [[package]] 363 | name = "csv" 364 | version = "1.3.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" 367 | dependencies = [ 368 | "csv-core", 369 | "itoa", 370 | "ryu", 371 | "serde", 372 | ] 373 | 374 | [[package]] 375 | name = "csv-core" 376 | version = "0.1.11" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" 379 | dependencies = [ 380 | "memchr", 381 | ] 382 | 383 | [[package]] 384 | name = "ctrlc" 385 | version = "3.4.4" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" 388 | dependencies = [ 389 | "nix", 390 | "windows-sys 0.52.0", 391 | ] 392 | 393 | [[package]] 394 | name = "cty" 395 | version = "0.2.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 398 | 399 | [[package]] 400 | name = "dyn-clone" 401 | version = "1.0.17" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" 404 | 405 | [[package]] 406 | name = "either" 407 | version = "1.11.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" 410 | 411 | [[package]] 412 | name = "encoding_rs" 413 | version = "0.8.34" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 416 | dependencies = [ 417 | "cfg-if", 418 | ] 419 | 420 | [[package]] 421 | name = "equivalent" 422 | version = "1.0.1" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 425 | 426 | [[package]] 427 | name = "errno" 428 | version = "0.3.8" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 431 | dependencies = [ 432 | "libc", 433 | "windows-sys 0.52.0", 434 | ] 435 | 436 | [[package]] 437 | name = "fallible-iterator" 438 | version = "0.2.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 441 | 442 | [[package]] 443 | name = "fastrand" 444 | version = "2.0.2" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" 447 | 448 | [[package]] 449 | name = "flate2" 450 | version = "1.0.28" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 453 | dependencies = [ 454 | "crc32fast", 455 | "miniz_oxide", 456 | ] 457 | 458 | [[package]] 459 | name = "fnv" 460 | version = "1.0.7" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 463 | 464 | [[package]] 465 | name = "foreign-types" 466 | version = "0.3.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 469 | dependencies = [ 470 | "foreign-types-shared", 471 | ] 472 | 473 | [[package]] 474 | name = "foreign-types-shared" 475 | version = "0.1.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 478 | 479 | [[package]] 480 | name = "form_urlencoded" 481 | version = "1.2.1" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 484 | dependencies = [ 485 | "percent-encoding", 486 | ] 487 | 488 | [[package]] 489 | name = "futures-channel" 490 | version = "0.3.30" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 493 | dependencies = [ 494 | "futures-core", 495 | "futures-sink", 496 | ] 497 | 498 | [[package]] 499 | name = "futures-core" 500 | version = "0.3.30" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 503 | 504 | [[package]] 505 | name = "futures-io" 506 | version = "0.3.30" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 509 | 510 | [[package]] 511 | name = "futures-sink" 512 | version = "0.3.30" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 515 | 516 | [[package]] 517 | name = "futures-task" 518 | version = "0.3.30" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 521 | 522 | [[package]] 523 | name = "futures-util" 524 | version = "0.3.30" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 527 | dependencies = [ 528 | "futures-core", 529 | "futures-io", 530 | "futures-sink", 531 | "futures-task", 532 | "memchr", 533 | "pin-project-lite", 534 | "pin-utils", 535 | "slab", 536 | ] 537 | 538 | [[package]] 539 | name = "fuzzy-matcher" 540 | version = "0.3.7" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 543 | dependencies = [ 544 | "thread_local", 545 | ] 546 | 547 | [[package]] 548 | name = "fxhash" 549 | version = "0.2.1" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 552 | dependencies = [ 553 | "byteorder", 554 | ] 555 | 556 | [[package]] 557 | name = "gimli" 558 | version = "0.27.3" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 561 | dependencies = [ 562 | "fallible-iterator", 563 | "stable_deref_trait", 564 | ] 565 | 566 | [[package]] 567 | name = "gimli" 568 | version = "0.28.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 571 | 572 | [[package]] 573 | name = "glob" 574 | version = "0.3.1" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 577 | 578 | [[package]] 579 | name = "goblin" 580 | version = "0.8.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "bb07a4ffed2093b118a525b1d8f5204ae274faed5604537caf7135d0f18d9887" 583 | dependencies = [ 584 | "log", 585 | "plain", 586 | "scroll", 587 | ] 588 | 589 | [[package]] 590 | name = "h2" 591 | version = "0.4.4" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" 594 | dependencies = [ 595 | "bytes", 596 | "fnv", 597 | "futures-core", 598 | "futures-sink", 599 | "futures-util", 600 | "http", 601 | "indexmap", 602 | "slab", 603 | "tokio", 604 | "tokio-util", 605 | "tracing", 606 | ] 607 | 608 | [[package]] 609 | name = "hashbrown" 610 | version = "0.14.3" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 613 | 614 | [[package]] 615 | name = "hermit-abi" 616 | version = "0.3.9" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 619 | 620 | [[package]] 621 | name = "http" 622 | version = "1.1.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 625 | dependencies = [ 626 | "bytes", 627 | "fnv", 628 | "itoa", 629 | ] 630 | 631 | [[package]] 632 | name = "http-body" 633 | version = "1.0.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 636 | dependencies = [ 637 | "bytes", 638 | "http", 639 | ] 640 | 641 | [[package]] 642 | name = "http-body-util" 643 | version = "0.1.1" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" 646 | dependencies = [ 647 | "bytes", 648 | "futures-core", 649 | "http", 650 | "http-body", 651 | "pin-project-lite", 652 | ] 653 | 654 | [[package]] 655 | name = "httparse" 656 | version = "1.8.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 659 | 660 | [[package]] 661 | name = "hyper" 662 | version = "1.3.1" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" 665 | dependencies = [ 666 | "bytes", 667 | "futures-channel", 668 | "futures-util", 669 | "h2", 670 | "http", 671 | "http-body", 672 | "httparse", 673 | "itoa", 674 | "pin-project-lite", 675 | "smallvec", 676 | "tokio", 677 | "want", 678 | ] 679 | 680 | [[package]] 681 | name = "hyper-tls" 682 | version = "0.6.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 685 | dependencies = [ 686 | "bytes", 687 | "http-body-util", 688 | "hyper", 689 | "hyper-util", 690 | "native-tls", 691 | "tokio", 692 | "tokio-native-tls", 693 | "tower-service", 694 | ] 695 | 696 | [[package]] 697 | name = "hyper-util" 698 | version = "0.1.3" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 701 | dependencies = [ 702 | "bytes", 703 | "futures-channel", 704 | "futures-util", 705 | "http", 706 | "http-body", 707 | "hyper", 708 | "pin-project-lite", 709 | "socket2", 710 | "tokio", 711 | "tower", 712 | "tower-service", 713 | "tracing", 714 | ] 715 | 716 | [[package]] 717 | name = "idna" 718 | version = "0.5.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 721 | dependencies = [ 722 | "unicode-bidi", 723 | "unicode-normalization", 724 | ] 725 | 726 | [[package]] 727 | name = "indexmap" 728 | version = "2.2.6" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 731 | dependencies = [ 732 | "equivalent", 733 | "hashbrown", 734 | ] 735 | 736 | [[package]] 737 | name = "inquire" 738 | version = "0.7.5" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" 741 | dependencies = [ 742 | "bitflags 2.5.0", 743 | "crossterm 0.25.0", 744 | "dyn-clone", 745 | "fuzzy-matcher", 746 | "fxhash", 747 | "newline-converter", 748 | "once_cell", 749 | "unicode-segmentation", 750 | "unicode-width", 751 | ] 752 | 753 | [[package]] 754 | name = "ipnet" 755 | version = "2.9.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 758 | 759 | [[package]] 760 | name = "itertools" 761 | version = "0.12.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 764 | dependencies = [ 765 | "either", 766 | ] 767 | 768 | [[package]] 769 | name = "itoa" 770 | version = "1.0.11" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 773 | 774 | [[package]] 775 | name = "jobserver" 776 | version = "0.1.31" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 779 | dependencies = [ 780 | "libc", 781 | ] 782 | 783 | [[package]] 784 | name = "js-sys" 785 | version = "0.3.69" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 788 | dependencies = [ 789 | "wasm-bindgen", 790 | ] 791 | 792 | [[package]] 793 | name = "json_compilation_db" 794 | version = "0.3.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "48a867c1f984f173b66f3bbd9cf6e160d7628f40e15dd97e51dac4748fe0ad9b" 797 | dependencies = [ 798 | "serde", 799 | "serde_json", 800 | "shell-words", 801 | "thiserror", 802 | ] 803 | 804 | [[package]] 805 | name = "lazy-init" 806 | version = "0.5.1" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "9f40963626ac12dcaf92afc15e4c3db624858c92fd9f8ba2125eaada3ac2706f" 809 | 810 | [[package]] 811 | name = "lazy_static" 812 | version = "1.4.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 815 | 816 | [[package]] 817 | name = "lazycell" 818 | version = "1.3.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 821 | 822 | [[package]] 823 | name = "lexopt" 824 | version = "0.3.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" 827 | 828 | [[package]] 829 | name = "libc" 830 | version = "0.2.153" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 833 | 834 | [[package]] 835 | name = "libloading" 836 | version = "0.8.3" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" 839 | dependencies = [ 840 | "cfg-if", 841 | "windows-targets 0.52.5", 842 | ] 843 | 844 | [[package]] 845 | name = "libmimalloc-sys" 846 | version = "0.1.37" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7" 849 | dependencies = [ 850 | "cc", 851 | "libc", 852 | ] 853 | 854 | [[package]] 855 | name = "linux-raw-sys" 856 | version = "0.4.13" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 859 | 860 | [[package]] 861 | name = "lock_api" 862 | version = "0.4.11" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 865 | dependencies = [ 866 | "autocfg", 867 | "scopeguard", 868 | ] 869 | 870 | [[package]] 871 | name = "log" 872 | version = "0.4.21" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 875 | 876 | [[package]] 877 | name = "memchr" 878 | version = "2.7.2" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 881 | 882 | [[package]] 883 | name = "memmap" 884 | version = "0.7.0" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 887 | dependencies = [ 888 | "libc", 889 | "winapi", 890 | ] 891 | 892 | [[package]] 893 | name = "mimalloc" 894 | version = "0.1.41" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d" 897 | dependencies = [ 898 | "libmimalloc-sys", 899 | ] 900 | 901 | [[package]] 902 | name = "mime" 903 | version = "0.3.17" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 906 | 907 | [[package]] 908 | name = "minimal-lexical" 909 | version = "0.2.1" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 912 | 913 | [[package]] 914 | name = "miniz_oxide" 915 | version = "0.7.2" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 918 | dependencies = [ 919 | "adler", 920 | ] 921 | 922 | [[package]] 923 | name = "mio" 924 | version = "0.8.11" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 927 | dependencies = [ 928 | "libc", 929 | "log", 930 | "wasi", 931 | "windows-sys 0.48.0", 932 | ] 933 | 934 | [[package]] 935 | name = "native-tls" 936 | version = "0.2.11" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 939 | dependencies = [ 940 | "lazy_static", 941 | "libc", 942 | "log", 943 | "openssl", 944 | "openssl-probe", 945 | "openssl-sys", 946 | "schannel", 947 | "security-framework", 948 | "security-framework-sys", 949 | "tempfile", 950 | ] 951 | 952 | [[package]] 953 | name = "newline-converter" 954 | version = "0.3.0" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" 957 | dependencies = [ 958 | "unicode-segmentation", 959 | ] 960 | 961 | [[package]] 962 | name = "nix" 963 | version = "0.28.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" 966 | dependencies = [ 967 | "bitflags 2.5.0", 968 | "cfg-if", 969 | "cfg_aliases", 970 | "libc", 971 | ] 972 | 973 | [[package]] 974 | name = "nom" 975 | version = "7.1.3" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 978 | dependencies = [ 979 | "memchr", 980 | "minimal-lexical", 981 | ] 982 | 983 | [[package]] 984 | name = "num-derive" 985 | version = "0.4.2" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 988 | dependencies = [ 989 | "proc-macro2", 990 | "quote", 991 | "syn", 992 | ] 993 | 994 | [[package]] 995 | name = "num-traits" 996 | version = "0.2.18" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 999 | dependencies = [ 1000 | "autocfg", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "num_cpus" 1005 | version = "1.16.0" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1008 | dependencies = [ 1009 | "hermit-abi", 1010 | "libc", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "object" 1015 | version = "0.30.4" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" 1018 | dependencies = [ 1019 | "flate2", 1020 | "memchr", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "object" 1025 | version = "0.32.2" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 1028 | dependencies = [ 1029 | "memchr", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "once_cell" 1034 | version = "1.19.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1037 | 1038 | [[package]] 1039 | name = "openssl" 1040 | version = "0.10.64" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" 1043 | dependencies = [ 1044 | "bitflags 2.5.0", 1045 | "cfg-if", 1046 | "foreign-types", 1047 | "libc", 1048 | "once_cell", 1049 | "openssl-macros", 1050 | "openssl-sys", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "openssl-macros" 1055 | version = "0.1.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1058 | dependencies = [ 1059 | "proc-macro2", 1060 | "quote", 1061 | "syn", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "openssl-probe" 1066 | version = "0.1.5" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1069 | 1070 | [[package]] 1071 | name = "openssl-sys" 1072 | version = "0.9.102" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" 1075 | dependencies = [ 1076 | "cc", 1077 | "libc", 1078 | "pkg-config", 1079 | "vcpkg", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "owning_ref" 1084 | version = "0.4.1" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" 1087 | dependencies = [ 1088 | "stable_deref_trait", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "parking_lot" 1093 | version = "0.12.1" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1096 | dependencies = [ 1097 | "lock_api", 1098 | "parking_lot_core", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "parking_lot_core" 1103 | version = "0.9.9" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 1106 | dependencies = [ 1107 | "cfg-if", 1108 | "libc", 1109 | "redox_syscall", 1110 | "smallvec", 1111 | "windows-targets 0.48.5", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "percent-encoding" 1116 | version = "2.3.1" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1119 | 1120 | [[package]] 1121 | name = "pin-project" 1122 | version = "1.1.5" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1125 | dependencies = [ 1126 | "pin-project-internal", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "pin-project-internal" 1131 | version = "1.1.5" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1134 | dependencies = [ 1135 | "proc-macro2", 1136 | "quote", 1137 | "syn", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "pin-project-lite" 1142 | version = "0.2.14" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1145 | 1146 | [[package]] 1147 | name = "pin-utils" 1148 | version = "0.1.0" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1151 | 1152 | [[package]] 1153 | name = "pkg-config" 1154 | version = "0.3.30" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 1157 | 1158 | [[package]] 1159 | name = "plain" 1160 | version = "0.2.3" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 1163 | 1164 | [[package]] 1165 | name = "proc-macro2" 1166 | version = "1.0.81" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 1169 | dependencies = [ 1170 | "unicode-ident", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "quote" 1175 | version = "1.0.36" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1178 | dependencies = [ 1179 | "proc-macro2", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "rayon" 1184 | version = "1.10.0" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1187 | dependencies = [ 1188 | "either", 1189 | "rayon-core", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "rayon-core" 1194 | version = "1.12.1" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1197 | dependencies = [ 1198 | "crossbeam-deque", 1199 | "crossbeam-utils", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "redox_syscall" 1204 | version = "0.4.1" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1207 | dependencies = [ 1208 | "bitflags 1.3.2", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "regex" 1213 | version = "1.10.4" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 1216 | dependencies = [ 1217 | "aho-corasick", 1218 | "memchr", 1219 | "regex-automata", 1220 | "regex-syntax", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "regex-automata" 1225 | version = "0.4.6" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 1228 | dependencies = [ 1229 | "aho-corasick", 1230 | "memchr", 1231 | "regex-syntax", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "regex-syntax" 1236 | version = "0.8.3" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 1239 | 1240 | [[package]] 1241 | name = "reqwest" 1242 | version = "0.12.4" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" 1245 | dependencies = [ 1246 | "base64", 1247 | "bytes", 1248 | "encoding_rs", 1249 | "futures-channel", 1250 | "futures-core", 1251 | "futures-util", 1252 | "h2", 1253 | "http", 1254 | "http-body", 1255 | "http-body-util", 1256 | "hyper", 1257 | "hyper-tls", 1258 | "hyper-util", 1259 | "ipnet", 1260 | "js-sys", 1261 | "log", 1262 | "mime", 1263 | "native-tls", 1264 | "once_cell", 1265 | "percent-encoding", 1266 | "pin-project-lite", 1267 | "rustls-pemfile", 1268 | "serde", 1269 | "serde_json", 1270 | "serde_urlencoded", 1271 | "sync_wrapper", 1272 | "system-configuration", 1273 | "tokio", 1274 | "tokio-native-tls", 1275 | "tower-service", 1276 | "url", 1277 | "wasm-bindgen", 1278 | "wasm-bindgen-futures", 1279 | "web-sys", 1280 | "winreg", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "rustc-demangle" 1285 | version = "0.1.23" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1288 | 1289 | [[package]] 1290 | name = "rustc-hash" 1291 | version = "1.1.0" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1294 | 1295 | [[package]] 1296 | name = "rustix" 1297 | version = "0.38.34" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1300 | dependencies = [ 1301 | "bitflags 2.5.0", 1302 | "errno", 1303 | "libc", 1304 | "linux-raw-sys", 1305 | "windows-sys 0.52.0", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "rustls-pemfile" 1310 | version = "2.1.2" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 1313 | dependencies = [ 1314 | "base64", 1315 | "rustls-pki-types", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "rustls-pki-types" 1320 | version = "1.5.0" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" 1323 | 1324 | [[package]] 1325 | name = "ryu" 1326 | version = "1.0.17" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 1329 | 1330 | [[package]] 1331 | name = "schannel" 1332 | version = "0.1.23" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 1335 | dependencies = [ 1336 | "windows-sys 0.52.0", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "scopeguard" 1341 | version = "1.2.0" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1344 | 1345 | [[package]] 1346 | name = "scroll" 1347 | version = "0.12.0" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 1350 | dependencies = [ 1351 | "scroll_derive", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "scroll_derive" 1356 | version = "0.12.0" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" 1359 | dependencies = [ 1360 | "proc-macro2", 1361 | "quote", 1362 | "syn", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "security-framework" 1367 | version = "2.10.0" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" 1370 | dependencies = [ 1371 | "bitflags 1.3.2", 1372 | "core-foundation", 1373 | "core-foundation-sys", 1374 | "libc", 1375 | "security-framework-sys", 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "security-framework-sys" 1380 | version = "2.10.0" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" 1383 | dependencies = [ 1384 | "core-foundation-sys", 1385 | "libc", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "serde" 1390 | version = "1.0.198" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" 1393 | dependencies = [ 1394 | "serde_derive", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "serde_derive" 1399 | version = "1.0.198" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" 1402 | dependencies = [ 1403 | "proc-macro2", 1404 | "quote", 1405 | "syn", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "serde_json" 1410 | version = "1.0.116" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" 1413 | dependencies = [ 1414 | "itoa", 1415 | "ryu", 1416 | "serde", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "serde_spanned" 1421 | version = "0.6.5" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 1424 | dependencies = [ 1425 | "serde", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "serde_urlencoded" 1430 | version = "0.7.1" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1433 | dependencies = [ 1434 | "form_urlencoded", 1435 | "itoa", 1436 | "ryu", 1437 | "serde", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "shell-words" 1442 | version = "1.1.0" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 1445 | 1446 | [[package]] 1447 | name = "shlex" 1448 | version = "1.3.0" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1451 | 1452 | [[package]] 1453 | name = "signal-hook" 1454 | version = "0.3.17" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1457 | dependencies = [ 1458 | "libc", 1459 | "signal-hook-registry", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "signal-hook-mio" 1464 | version = "0.2.3" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 1467 | dependencies = [ 1468 | "libc", 1469 | "mio", 1470 | "signal-hook", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "signal-hook-registry" 1475 | version = "1.4.2" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1478 | dependencies = [ 1479 | "libc", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "slab" 1484 | version = "0.4.9" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1487 | dependencies = [ 1488 | "autocfg", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "smallvec" 1493 | version = "1.13.2" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1496 | 1497 | [[package]] 1498 | name = "smawk" 1499 | version = "0.3.2" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" 1502 | 1503 | [[package]] 1504 | name = "socket2" 1505 | version = "0.5.6" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" 1508 | dependencies = [ 1509 | "libc", 1510 | "windows-sys 0.52.0", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "stable_deref_trait" 1515 | version = "1.2.0" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1518 | 1519 | [[package]] 1520 | name = "static_assertions" 1521 | version = "1.1.0" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1524 | 1525 | [[package]] 1526 | name = "syn" 1527 | version = "2.0.60" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 1530 | dependencies = [ 1531 | "proc-macro2", 1532 | "quote", 1533 | "unicode-ident", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "sync_wrapper" 1538 | version = "0.1.2" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1541 | 1542 | [[package]] 1543 | name = "system-configuration" 1544 | version = "0.5.1" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1547 | dependencies = [ 1548 | "bitflags 1.3.2", 1549 | "core-foundation", 1550 | "system-configuration-sys", 1551 | ] 1552 | 1553 | [[package]] 1554 | name = "system-configuration-sys" 1555 | version = "0.5.0" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1558 | dependencies = [ 1559 | "core-foundation-sys", 1560 | "libc", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "tempfile" 1565 | version = "3.10.1" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 1568 | dependencies = [ 1569 | "cfg-if", 1570 | "fastrand", 1571 | "rustix", 1572 | "windows-sys 0.52.0", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "textwrap" 1577 | version = "0.16.1" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1580 | dependencies = [ 1581 | "smawk", 1582 | "unicode-linebreak", 1583 | "unicode-width", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "thiserror" 1588 | version = "1.0.59" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" 1591 | dependencies = [ 1592 | "thiserror-impl", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "thiserror-impl" 1597 | version = "1.0.59" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" 1600 | dependencies = [ 1601 | "proc-macro2", 1602 | "quote", 1603 | "syn", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "thread_local" 1608 | version = "1.1.8" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1611 | dependencies = [ 1612 | "cfg-if", 1613 | "once_cell", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "tinyvec" 1618 | version = "1.6.0" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1621 | dependencies = [ 1622 | "tinyvec_macros", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "tinyvec_macros" 1627 | version = "0.1.1" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1630 | 1631 | [[package]] 1632 | name = "tokio" 1633 | version = "1.37.0" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 1636 | dependencies = [ 1637 | "backtrace", 1638 | "bytes", 1639 | "libc", 1640 | "mio", 1641 | "num_cpus", 1642 | "pin-project-lite", 1643 | "socket2", 1644 | "windows-sys 0.48.0", 1645 | ] 1646 | 1647 | [[package]] 1648 | name = "tokio-native-tls" 1649 | version = "0.3.1" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1652 | dependencies = [ 1653 | "native-tls", 1654 | "tokio", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "tokio-util" 1659 | version = "0.7.10" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1662 | dependencies = [ 1663 | "bytes", 1664 | "futures-core", 1665 | "futures-sink", 1666 | "pin-project-lite", 1667 | "tokio", 1668 | "tracing", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "toml" 1673 | version = "0.8.12" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" 1676 | dependencies = [ 1677 | "serde", 1678 | "serde_spanned", 1679 | "toml_datetime", 1680 | "toml_edit", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "toml_datetime" 1685 | version = "0.6.5" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 1688 | dependencies = [ 1689 | "serde", 1690 | ] 1691 | 1692 | [[package]] 1693 | name = "toml_edit" 1694 | version = "0.22.12" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" 1697 | dependencies = [ 1698 | "indexmap", 1699 | "serde", 1700 | "serde_spanned", 1701 | "toml_datetime", 1702 | "winnow", 1703 | ] 1704 | 1705 | [[package]] 1706 | name = "tower" 1707 | version = "0.4.13" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1710 | dependencies = [ 1711 | "futures-core", 1712 | "futures-util", 1713 | "pin-project", 1714 | "pin-project-lite", 1715 | "tokio", 1716 | "tower-layer", 1717 | "tower-service", 1718 | "tracing", 1719 | ] 1720 | 1721 | [[package]] 1722 | name = "tower-layer" 1723 | version = "0.3.2" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1726 | 1727 | [[package]] 1728 | name = "tower-service" 1729 | version = "0.3.2" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1732 | 1733 | [[package]] 1734 | name = "tracing" 1735 | version = "0.1.40" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1738 | dependencies = [ 1739 | "log", 1740 | "pin-project-lite", 1741 | "tracing-core", 1742 | ] 1743 | 1744 | [[package]] 1745 | name = "tracing-core" 1746 | version = "0.1.32" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1749 | dependencies = [ 1750 | "once_cell", 1751 | ] 1752 | 1753 | [[package]] 1754 | name = "try-lock" 1755 | version = "0.2.5" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1758 | 1759 | [[package]] 1760 | name = "unicode-bidi" 1761 | version = "0.3.15" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1764 | 1765 | [[package]] 1766 | name = "unicode-ident" 1767 | version = "1.0.12" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1770 | 1771 | [[package]] 1772 | name = "unicode-linebreak" 1773 | version = "0.1.5" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 1776 | 1777 | [[package]] 1778 | name = "unicode-normalization" 1779 | version = "0.1.23" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1782 | dependencies = [ 1783 | "tinyvec", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "unicode-segmentation" 1788 | version = "1.11.0" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1791 | 1792 | [[package]] 1793 | name = "unicode-width" 1794 | version = "0.1.11" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 1797 | 1798 | [[package]] 1799 | name = "url" 1800 | version = "2.5.0" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1803 | dependencies = [ 1804 | "form_urlencoded", 1805 | "idna", 1806 | "percent-encoding", 1807 | ] 1808 | 1809 | [[package]] 1810 | name = "vcpkg" 1811 | version = "0.2.15" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1814 | 1815 | [[package]] 1816 | name = "viking" 1817 | version = "1.3.2" 1818 | dependencies = [ 1819 | "addr2line 0.19.0", 1820 | "anyhow", 1821 | "argh", 1822 | "bad64", 1823 | "capstone", 1824 | "colored", 1825 | "cpp_demangle", 1826 | "crossterm 0.27.0", 1827 | "csv", 1828 | "ctrlc", 1829 | "goblin", 1830 | "inquire", 1831 | "itertools", 1832 | "json_compilation_db", 1833 | "lazy-init", 1834 | "lazy_static", 1835 | "lexopt", 1836 | "memmap", 1837 | "mimalloc", 1838 | "owning_ref", 1839 | "rayon", 1840 | "reqwest", 1841 | "rustc-hash", 1842 | "serde", 1843 | "serde_json", 1844 | "textwrap", 1845 | "toml", 1846 | ] 1847 | 1848 | [[package]] 1849 | name = "want" 1850 | version = "0.3.1" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1853 | dependencies = [ 1854 | "try-lock", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "wasi" 1859 | version = "0.11.0+wasi-snapshot-preview1" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1862 | 1863 | [[package]] 1864 | name = "wasm-bindgen" 1865 | version = "0.2.92" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1868 | dependencies = [ 1869 | "cfg-if", 1870 | "wasm-bindgen-macro", 1871 | ] 1872 | 1873 | [[package]] 1874 | name = "wasm-bindgen-backend" 1875 | version = "0.2.92" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1878 | dependencies = [ 1879 | "bumpalo", 1880 | "log", 1881 | "once_cell", 1882 | "proc-macro2", 1883 | "quote", 1884 | "syn", 1885 | "wasm-bindgen-shared", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "wasm-bindgen-futures" 1890 | version = "0.4.42" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1893 | dependencies = [ 1894 | "cfg-if", 1895 | "js-sys", 1896 | "wasm-bindgen", 1897 | "web-sys", 1898 | ] 1899 | 1900 | [[package]] 1901 | name = "wasm-bindgen-macro" 1902 | version = "0.2.92" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1905 | dependencies = [ 1906 | "quote", 1907 | "wasm-bindgen-macro-support", 1908 | ] 1909 | 1910 | [[package]] 1911 | name = "wasm-bindgen-macro-support" 1912 | version = "0.2.92" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1915 | dependencies = [ 1916 | "proc-macro2", 1917 | "quote", 1918 | "syn", 1919 | "wasm-bindgen-backend", 1920 | "wasm-bindgen-shared", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "wasm-bindgen-shared" 1925 | version = "0.2.92" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1928 | 1929 | [[package]] 1930 | name = "web-sys" 1931 | version = "0.3.69" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1934 | dependencies = [ 1935 | "js-sys", 1936 | "wasm-bindgen", 1937 | ] 1938 | 1939 | [[package]] 1940 | name = "winapi" 1941 | version = "0.3.9" 1942 | source = "registry+https://github.com/rust-lang/crates.io-index" 1943 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1944 | dependencies = [ 1945 | "winapi-i686-pc-windows-gnu", 1946 | "winapi-x86_64-pc-windows-gnu", 1947 | ] 1948 | 1949 | [[package]] 1950 | name = "winapi-i686-pc-windows-gnu" 1951 | version = "0.4.0" 1952 | source = "registry+https://github.com/rust-lang/crates.io-index" 1953 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1954 | 1955 | [[package]] 1956 | name = "winapi-x86_64-pc-windows-gnu" 1957 | version = "0.4.0" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1960 | 1961 | [[package]] 1962 | name = "windows-sys" 1963 | version = "0.48.0" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1966 | dependencies = [ 1967 | "windows-targets 0.48.5", 1968 | ] 1969 | 1970 | [[package]] 1971 | name = "windows-sys" 1972 | version = "0.52.0" 1973 | source = "registry+https://github.com/rust-lang/crates.io-index" 1974 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1975 | dependencies = [ 1976 | "windows-targets 0.52.5", 1977 | ] 1978 | 1979 | [[package]] 1980 | name = "windows-targets" 1981 | version = "0.48.5" 1982 | source = "registry+https://github.com/rust-lang/crates.io-index" 1983 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1984 | dependencies = [ 1985 | "windows_aarch64_gnullvm 0.48.5", 1986 | "windows_aarch64_msvc 0.48.5", 1987 | "windows_i686_gnu 0.48.5", 1988 | "windows_i686_msvc 0.48.5", 1989 | "windows_x86_64_gnu 0.48.5", 1990 | "windows_x86_64_gnullvm 0.48.5", 1991 | "windows_x86_64_msvc 0.48.5", 1992 | ] 1993 | 1994 | [[package]] 1995 | name = "windows-targets" 1996 | version = "0.52.5" 1997 | source = "registry+https://github.com/rust-lang/crates.io-index" 1998 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1999 | dependencies = [ 2000 | "windows_aarch64_gnullvm 0.52.5", 2001 | "windows_aarch64_msvc 0.52.5", 2002 | "windows_i686_gnu 0.52.5", 2003 | "windows_i686_gnullvm", 2004 | "windows_i686_msvc 0.52.5", 2005 | "windows_x86_64_gnu 0.52.5", 2006 | "windows_x86_64_gnullvm 0.52.5", 2007 | "windows_x86_64_msvc 0.52.5", 2008 | ] 2009 | 2010 | [[package]] 2011 | name = "windows_aarch64_gnullvm" 2012 | version = "0.48.5" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2015 | 2016 | [[package]] 2017 | name = "windows_aarch64_gnullvm" 2018 | version = "0.52.5" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 2021 | 2022 | [[package]] 2023 | name = "windows_aarch64_msvc" 2024 | version = "0.48.5" 2025 | source = "registry+https://github.com/rust-lang/crates.io-index" 2026 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2027 | 2028 | [[package]] 2029 | name = "windows_aarch64_msvc" 2030 | version = "0.52.5" 2031 | source = "registry+https://github.com/rust-lang/crates.io-index" 2032 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 2033 | 2034 | [[package]] 2035 | name = "windows_i686_gnu" 2036 | version = "0.48.5" 2037 | source = "registry+https://github.com/rust-lang/crates.io-index" 2038 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2039 | 2040 | [[package]] 2041 | name = "windows_i686_gnu" 2042 | version = "0.52.5" 2043 | source = "registry+https://github.com/rust-lang/crates.io-index" 2044 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 2045 | 2046 | [[package]] 2047 | name = "windows_i686_gnullvm" 2048 | version = "0.52.5" 2049 | source = "registry+https://github.com/rust-lang/crates.io-index" 2050 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 2051 | 2052 | [[package]] 2053 | name = "windows_i686_msvc" 2054 | version = "0.48.5" 2055 | source = "registry+https://github.com/rust-lang/crates.io-index" 2056 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2057 | 2058 | [[package]] 2059 | name = "windows_i686_msvc" 2060 | version = "0.52.5" 2061 | source = "registry+https://github.com/rust-lang/crates.io-index" 2062 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 2063 | 2064 | [[package]] 2065 | name = "windows_x86_64_gnu" 2066 | version = "0.48.5" 2067 | source = "registry+https://github.com/rust-lang/crates.io-index" 2068 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2069 | 2070 | [[package]] 2071 | name = "windows_x86_64_gnu" 2072 | version = "0.52.5" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 2075 | 2076 | [[package]] 2077 | name = "windows_x86_64_gnullvm" 2078 | version = "0.48.5" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2081 | 2082 | [[package]] 2083 | name = "windows_x86_64_gnullvm" 2084 | version = "0.52.5" 2085 | source = "registry+https://github.com/rust-lang/crates.io-index" 2086 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 2087 | 2088 | [[package]] 2089 | name = "windows_x86_64_msvc" 2090 | version = "0.48.5" 2091 | source = "registry+https://github.com/rust-lang/crates.io-index" 2092 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2093 | 2094 | [[package]] 2095 | name = "windows_x86_64_msvc" 2096 | version = "0.52.5" 2097 | source = "registry+https://github.com/rust-lang/crates.io-index" 2098 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 2099 | 2100 | [[package]] 2101 | name = "winnow" 2102 | version = "0.6.6" 2103 | source = "registry+https://github.com/rust-lang/crates.io-index" 2104 | checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" 2105 | dependencies = [ 2106 | "memchr", 2107 | ] 2108 | 2109 | [[package]] 2110 | name = "winreg" 2111 | version = "0.52.0" 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" 2113 | checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" 2114 | dependencies = [ 2115 | "cfg-if", 2116 | "windows-sys 0.48.0", 2117 | ] 2118 | -------------------------------------------------------------------------------- /viking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "viking" 3 | version = "1.3.2" 4 | edition = "2021" 5 | 6 | [profile.release] 7 | debug = 1 8 | lto = "thin" 9 | 10 | [dependencies] 11 | addr2line = "0.19.0" 12 | anyhow = "1.0" 13 | argh = "0.1.6" 14 | bad64 = "0.9.0" 15 | capstone = { git = "https://github.com/leoetlino/capstone-rs", rev = "f5aa278e1982bca86a67ac8b8550ab1dd70f7d9d" } 16 | colored = "2" 17 | cpp_demangle = "0.4.0" 18 | crossterm = "0.27.0" 19 | csv = "1.1" 20 | ctrlc = "3.2.1" 21 | goblin = "0.8.0" 22 | inquire = "0.7.4" 23 | itertools = "0.12.1" 24 | json_compilation_db = "0.3.0" 25 | lazy-init = "0.5.0" 26 | lazy_static = "1.4.0" 27 | lexopt = "0.3" 28 | memmap = "0.7" 29 | mimalloc = { version = "*", default-features = false } 30 | owning_ref = "0.4.1" 31 | rayon = "1.5.1" 32 | reqwest = { version = "0.12.3", features = ["blocking", "json"] } 33 | rustc-hash = "1.1.0" 34 | textwrap = "0.16" 35 | toml = "0.8.12" 36 | serde = { version = "1.0", features = ["derive"] } 37 | serde_json = "1.0.78" 38 | 39 | [[bin]] 40 | name = "check" 41 | path = "src/tools/check.rs" 42 | 43 | [[bin]] 44 | name = "listsym" 45 | path = "src/tools/list_symbols.rs" 46 | 47 | [[bin]] 48 | name = "decompme" 49 | path = "src/tools/decompme.rs" 50 | -------------------------------------------------------------------------------- /viking/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 leoetlino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /viking/src/capstone_utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use capstone as cs; 3 | use cs::arch::arm64::{Arm64Insn, Arm64OpMem, Arm64Operand, Arm64OperandType}; 4 | use cs::{arch::ArchOperand, RegId}; 5 | 6 | pub fn translate_cs_error(err: cs::Error) -> Result { 7 | bail!("capstone error: {}", err) 8 | } 9 | 10 | #[inline] 11 | pub fn map_two<'a, T, R, F: FnMut(&'a T) -> R>(x: &'a T, y: &'a T, mut f: F) -> (R, R) { 12 | (f(x), f(y)) 13 | } 14 | 15 | #[inline] 16 | pub fn map_pair<'a, T, R, F: FnMut(&'a T) -> R>(pair: &'a (T, T), f: F) -> (R, R) { 17 | map_two(&pair.0, &pair.1, f) 18 | } 19 | 20 | #[inline] 21 | pub fn try_map_two<'a, T, R, F: FnMut(&'a T) -> Result>( 22 | x: &'a T, 23 | y: &'a T, 24 | mut f: F, 25 | ) -> Result<(R, R)> { 26 | Ok(( 27 | f(x).or_else(translate_cs_error)?, 28 | f(y).or_else(translate_cs_error)?, 29 | )) 30 | } 31 | 32 | /// Checks if `id` is in [start, end] (inclusive range). 33 | #[inline] 34 | pub fn is_id_in_range(start: Arm64Insn, end: Arm64Insn, id: Arm64Insn) -> bool { 35 | let range = (start as u32)..=(end as u32); 36 | range.contains(&(id as u32)) 37 | } 38 | 39 | /// Used to make accessing arch-specific data less cumbersome. 40 | pub trait CsArchOperandUtil { 41 | fn arm64(&self) -> &Arm64Operand; 42 | } 43 | 44 | impl CsArchOperandUtil for ArchOperand { 45 | fn arm64(&self) -> &Arm64Operand { 46 | match self { 47 | Self::Arm64Operand(x) => x, 48 | _ => unreachable!(), 49 | } 50 | } 51 | } 52 | 53 | /// Used to make accessing arch-specific data less cumbersome. 54 | pub trait CsArm64OperandTypeUtil { 55 | fn reg(&self) -> RegId; 56 | fn imm(&self) -> i64; 57 | fn try_mem(&self) -> Option; 58 | fn mem(&self) -> Arm64OpMem; 59 | } 60 | 61 | impl CsArm64OperandTypeUtil for Arm64OperandType { 62 | fn reg(&self) -> RegId { 63 | match self { 64 | Self::Reg(x) => *x, 65 | _ => panic!("expected Reg, got {:#?}", &self), 66 | } 67 | } 68 | 69 | fn imm(&self) -> i64 { 70 | match self { 71 | Self::Imm(x) => *x, 72 | _ => panic!("expected Imm, got {:#?}", &self), 73 | } 74 | } 75 | 76 | fn try_mem(&self) -> Option { 77 | match self { 78 | Self::Mem(x) => Some(*x), 79 | _ => None, 80 | } 81 | } 82 | 83 | fn mem(&self) -> Arm64OpMem { 84 | match self { 85 | Self::Mem(x) => *x, 86 | _ => panic!("expected Mem, got {:#?}", &self), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /viking/src/checks.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{ensure, Result}; 2 | use capstone as cs; 3 | use cs::arch::arm64::{Arm64Insn, Arm64Operand, Arm64OperandType}; 4 | use lazy_init::Lazy; 5 | use rustc_hash::FxHashMap; 6 | use std::collections::{HashMap, HashSet}; 7 | use std::convert::TryInto; 8 | use std::iter::zip; 9 | use std::path::{Path, PathBuf}; 10 | 11 | use crate::{capstone_utils::*, elf, functions, repo, ui}; 12 | 13 | struct DataSymbol { 14 | /// Address of the symbol in the original executable. 15 | pub addr: u64, 16 | /// Name of the symbol in our source code. 17 | pub name: String, 18 | /// Size of the symbol in our source code (according to ELF info). 19 | pub size: u64, 20 | } 21 | 22 | /// Keeps track of known data symbols so that data loads can be validated. 23 | #[derive(Default)] 24 | struct KnownDataSymbolMap { 25 | /// Symbols. Must be sorted by address. 26 | symbols: Vec, 27 | } 28 | 29 | impl KnownDataSymbolMap { 30 | fn new() -> Self { 31 | Default::default() 32 | } 33 | 34 | fn load(&mut self, csv_path: &Path, decomp_symtab: &elf::SymbolTableByName) -> Result<()> { 35 | let mut reader = csv::ReaderBuilder::new() 36 | .has_headers(false) 37 | .quoting(false) 38 | .from_path(csv_path)?; 39 | for (line, maybe_record) in reader.records().enumerate() { 40 | let record = &maybe_record?; 41 | ensure!( 42 | record.len() == 2, 43 | "invalid number of fields on line {}", 44 | line 45 | ); 46 | 47 | let addr = functions::parse_address(&record[0])?; 48 | let name = &record[1]; 49 | 50 | let symbol = decomp_symtab.get(name); 51 | // Ignore missing symbols. 52 | if symbol.is_none() { 53 | continue; 54 | } 55 | let symbol = symbol.unwrap(); 56 | 57 | self.symbols.push(DataSymbol { 58 | addr, 59 | name: name.to_string(), 60 | size: symbol.st_size, 61 | }); 62 | } 63 | self.symbols.sort_by_key(|sym| sym.addr); 64 | Ok(()) 65 | } 66 | 67 | /// If addr is part of a known data symbol, this function returns the corresponding symbol. 68 | fn get_symbol(&self, addr: u64) -> Option<&DataSymbol> { 69 | // Perform a binary search since `symbols` is sorted. 70 | let mut a: isize = 0; 71 | let mut b: isize = self.symbols.len() as isize - 1; 72 | while a <= b { 73 | let m = a + (b - a) / 2; 74 | 75 | let mid_symbol = &self.symbols[m as usize]; 76 | let mid_addr_begin = mid_symbol.addr; 77 | let mid_addr_end = mid_addr_begin + mid_symbol.size; 78 | 79 | if mid_addr_begin <= addr && addr < mid_addr_end { 80 | return Some(mid_symbol); 81 | } 82 | if addr <= mid_addr_begin { 83 | b = m - 1; 84 | } else if addr >= mid_addr_end { 85 | a = m + 1; 86 | } else { 87 | break; 88 | } 89 | } 90 | None 91 | } 92 | } 93 | 94 | fn get_data_symbol_csv_path(version: Option<&str>) -> Result { 95 | Ok(repo::get_data_path(version)?.join("data_symbols.csv")) 96 | } 97 | 98 | #[derive(Debug)] 99 | pub struct ReferenceDiff { 100 | pub referenced_symbol: u64, 101 | pub expected_ref_in_decomp: u64, 102 | pub actual_ref_in_decomp: u64, 103 | 104 | pub expected_symbol_name: String, 105 | pub actual_symbol_name: String, 106 | } 107 | 108 | impl std::fmt::Display for ReferenceDiff { 109 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 110 | write!( 111 | f, 112 | "incorrect reference; expected to see {ref} {ref_name}\n\ 113 | --> decomp source code is referencing {actual} {actual_name}\n\ 114 | --> expected to see {expected} to match original code", 115 | ref=ui::format_address(self.referenced_symbol), 116 | ref_name=ui::format_symbol_name(&self.expected_symbol_name), 117 | expected=ui::format_address(self.expected_ref_in_decomp), 118 | actual=ui::format_address(self.actual_ref_in_decomp), 119 | actual_name=ui::format_symbol_name(&self.actual_symbol_name), 120 | ) 121 | } 122 | } 123 | 124 | #[derive(Debug)] 125 | pub enum MismatchCause { 126 | FunctionSize, 127 | Register, 128 | Mnemonic, 129 | BranchTarget, 130 | FunctionCall(ReferenceDiff), 131 | DataReference(ReferenceDiff), 132 | Immediate, 133 | Unknown, 134 | } 135 | 136 | impl std::fmt::Display for MismatchCause { 137 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 138 | match &self { 139 | Self::FunctionSize => write!(f, "wrong function size"), 140 | Self::Register => write!(f, "wrong register"), 141 | Self::Mnemonic => write!(f, "wrong mnemonic"), 142 | Self::BranchTarget => write!(f, "wrong branch target"), 143 | Self::FunctionCall(diff) => write!(f, "wrong function call\n{}", diff), 144 | Self::DataReference(diff) => write!(f, "wrong data reference\n{}", diff), 145 | Self::Immediate => write!(f, "wrong immediate"), 146 | Self::Unknown => write!(f, "unknown reason"), 147 | } 148 | } 149 | } 150 | 151 | #[derive(Debug)] 152 | pub struct Mismatch { 153 | pub addr_orig: u64, 154 | pub addr_decomp: u64, 155 | pub cause: MismatchCause, 156 | } 157 | 158 | impl std::fmt::Display for Mismatch { 159 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 160 | write!( 161 | f, 162 | "mismatch at {}: {}", 163 | ui::format_address(self.addr_orig), 164 | self.cause, 165 | ) 166 | } 167 | } 168 | 169 | pub struct FunctionChecker<'a, 'functions, 'orig_elf, 'decomp_elf> { 170 | pub decomp_elf: &'decomp_elf elf::OwnedElf, 171 | pub decomp_symtab: &'a elf::SymbolTableByName<'decomp_elf>, 172 | decomp_glob_data_table: elf::GlobDataTable, 173 | 174 | // Optional, only initialized when a mismatch is detected. 175 | decomp_addr_to_name_map: Lazy>, 176 | 177 | known_data_symbols: KnownDataSymbolMap, 178 | known_functions: FxHashMap, 179 | 180 | pub orig_elf: &'orig_elf elf::OwnedElf, 181 | orig_got_section: Option<&'orig_elf goblin::elf::SectionHeader>, 182 | } 183 | 184 | impl<'a, 'functions, 'orig_elf, 'decomp_elf> 185 | FunctionChecker<'a, 'functions, 'orig_elf, 'decomp_elf> 186 | { 187 | pub fn new( 188 | orig_elf: &'orig_elf elf::OwnedElf, 189 | decomp_elf: &'decomp_elf elf::OwnedElf, 190 | decomp_symtab: &'a elf::SymbolTableByName<'decomp_elf>, 191 | decomp_glob_data_table: elf::GlobDataTable, 192 | functions: &'functions [functions::Info], 193 | version: Option<&str>, 194 | ) -> Result { 195 | let mut known_data_symbols = KnownDataSymbolMap::new(); 196 | known_data_symbols.load(get_data_symbol_csv_path(version)?.as_path(), decomp_symtab)?; 197 | 198 | let known_functions = functions::make_known_function_map(functions); 199 | 200 | let orig_got_section = elf::find_section(orig_elf, ".got").ok(); 201 | 202 | Ok(FunctionChecker { 203 | decomp_elf, 204 | decomp_symtab, 205 | decomp_glob_data_table, 206 | decomp_addr_to_name_map: Lazy::new(), 207 | 208 | known_data_symbols, 209 | known_functions, 210 | 211 | orig_elf, 212 | orig_got_section, 213 | }) 214 | } 215 | 216 | pub fn check( 217 | &self, 218 | cs: &mut cs::Capstone, 219 | orig_fn: &elf::Function, 220 | decomp_fn: &elf::Function, 221 | ) -> Result> { 222 | // Keep track of registers that are used with ADRP so that we can check global data 223 | // references even when data is not placed at the same addresses 224 | // as in the original executable. 225 | #[derive(Default)] 226 | struct State { 227 | gprs1: HashMap, 228 | gprs2: HashMap, 229 | adrp_pair_registers: HashSet, 230 | } 231 | 232 | impl State { 233 | fn forget_modified_registers(&mut self, detail: &cs::InsnDetail) { 234 | for reg in detail.regs_write() { 235 | self.adrp_pair_registers.remove(reg); 236 | } 237 | } 238 | } 239 | 240 | let mut state = State::default(); 241 | 242 | if orig_fn.code.len() != decomp_fn.code.len() { 243 | return Ok(Some(Mismatch { 244 | addr_orig: orig_fn.addr, 245 | addr_decomp: decomp_fn.addr, 246 | cause: MismatchCause::FunctionSize, 247 | })); 248 | } 249 | 250 | let mut instructions = try_map_two(&orig_fn, &decomp_fn, |func| { 251 | cs.disasm_iter(func.code, func.addr) 252 | })?; 253 | 254 | // Check every pair of instructions. 255 | while let (Some(i1), Some(i2)) = (instructions.0.next(), instructions.1.next()) { 256 | let ids = map_two(&i1, &i2, |i| i.id().0); 257 | let detail = try_map_two(&i1, &i2, |insn| cs.insn_detail(insn))?; 258 | let arch_detail = map_pair(&detail, |d| d.arch_detail()); 259 | let ops = map_pair(&arch_detail, |a| a.arm64().unwrap().operands_ref()); 260 | 261 | if ids.0 != ids.1 { 262 | return Self::make_mismatch(&i1, &i2, MismatchCause::Mnemonic); 263 | } 264 | 265 | let id = ids.0; 266 | 267 | match id.into() { 268 | // Branches or function calls. 269 | Arm64Insn::ARM64_INS_B | Arm64Insn::ARM64_INS_BL => { 270 | let target = 271 | map_pair(&ops, |ops| Arm64Operand::from(&ops[0]).op_type.imm() as u64); 272 | 273 | // If we are branching outside the function, this is likely a tail call. 274 | // Treat it as a function call. 275 | if !orig_fn.get_addr_range().contains(&target.0) { 276 | if let Some(mismatch_cause) = self.check_function_call(target.0, target.1) { 277 | return Self::make_mismatch(&i1, &i2, mismatch_cause); 278 | } 279 | } else { 280 | // Otherwise, it's a simple branch, and both targets must match. 281 | if i1.bytes() != i2.bytes() { 282 | return Self::make_mismatch(&i1, &i2, MismatchCause::BranchTarget); 283 | } 284 | } 285 | } 286 | 287 | // Catch ADRP + (ADD/load/store) instruction pairs. 288 | Arm64Insn::ARM64_INS_ADRP => { 289 | let reg = map_pair(&ops, |ops| Arm64Operand::from(&ops[0]).op_type.reg()); 290 | let imm = 291 | map_pair(&ops, |ops| Arm64Operand::from(&ops[1]).op_type.imm() as u64); 292 | 293 | if reg.0 != reg.1 { 294 | return Self::make_mismatch(&i1, &i2, MismatchCause::Register); 295 | } 296 | 297 | state.gprs1.insert(reg.0, imm.0); 298 | state.gprs2.insert(reg.1, imm.1); 299 | state.adrp_pair_registers.insert(reg.0); 300 | } 301 | 302 | // Catch ADRP + ADD instruction pairs. 303 | Arm64Insn::ARM64_INS_ADD => { 304 | let mut diff_ok = false; 305 | 306 | if ops.0.len() == 3 && ops.1.len() == 3 { 307 | let dest_reg = 308 | map_pair(&ops, |ops| Arm64Operand::from(&ops[0]).op_type.reg()); 309 | let reg = map_pair(&ops, |ops| Arm64Operand::from(&ops[1]).op_type.reg()); 310 | 311 | if let Arm64OperandType::Imm(_) = Arm64Operand::from(&ops.0[2]).op_type { 312 | let imm = 313 | map_pair(&ops, |ops| Arm64Operand::from(&ops[2]).op_type.imm()); 314 | 315 | if dest_reg.0 != dest_reg.1 || reg.0 != reg.1 { 316 | return Self::make_mismatch(&i1, &i2, MismatchCause::Register); 317 | } 318 | 319 | // Is this an ADRP pair we can check? 320 | if state.adrp_pair_registers.contains(®.0) { 321 | let orig_addr = state.gprs1[®.0] + imm.0 as u64; 322 | let decomp_addr = state.gprs2[®.1] + imm.1 as u64; 323 | 324 | if let Some(mismatch_cause) = 325 | self.check_data_symbol(orig_addr, decomp_addr) 326 | { 327 | return Self::make_mismatch(&i1, &i2, mismatch_cause); 328 | } 329 | 330 | // If the data symbol reference matches, allow the instructions to be different. 331 | diff_ok = true; 332 | } 333 | } 334 | } 335 | 336 | if !diff_ok && i1.bytes() != i2.bytes() { 337 | return Self::make_mismatch(&i1, &i2, MismatchCause::Unknown); 338 | } 339 | 340 | state.forget_modified_registers(&detail.0); 341 | } 342 | 343 | // Loads and stores (single or paired). 344 | id if is_id_in_range(Arm64Insn::ARM64_INS_LD1, Arm64Insn::ARM64_INS_LDXRH, id) 345 | || is_id_in_range(Arm64Insn::ARM64_INS_ST1, Arm64Insn::ARM64_INS_STXR, id) => 346 | { 347 | let mut diff_ok = false; 348 | 349 | // Check all operands for mismatches, except the Arm64OpMem which will be checked later. 350 | let mut mem = (None, None); 351 | for (op1, op2) in zip(ops.0, ops.1) { 352 | let op1 = Arm64Operand::from(op1); 353 | let op2 = Arm64Operand::from(op2); 354 | if let Some(mem1) = op1.op_type.try_mem() { 355 | if let Some(mem2) = op2.op_type.try_mem() { 356 | ensure!( 357 | mem.0.is_none() && mem.1.is_none(), 358 | "found more than one OpMem" 359 | ); 360 | mem.0 = Some(mem1); 361 | mem.1 = Some(mem2); 362 | continue; 363 | } 364 | } 365 | 366 | if op1 != op2 { 367 | return Self::make_mismatch(&i1, &i2, MismatchCause::Unknown); 368 | } 369 | } 370 | 371 | ensure!(mem.0.is_some() && mem.1.is_some(), "didn't find an OpMem"); 372 | 373 | let mem = (mem.0.unwrap(), mem.1.unwrap()); 374 | 375 | if mem.0.base() != mem.1.base() { 376 | return Self::make_mismatch(&i1, &i2, MismatchCause::Register); 377 | } 378 | 379 | let reg = mem.0.base(); 380 | 381 | // Is this an ADRP pair we can check? 382 | if state.adrp_pair_registers.contains(®) { 383 | let orig_addr_ptr = (state.gprs1[®] as i64 + mem.0.disp() as i64) as u64; 384 | let decomp_addr_ptr = 385 | (state.gprs2[®] as i64 + mem.1.disp() as i64) as u64; 386 | 387 | if let Some(mismatch_cause) = 388 | self.check_data_symbol_ptr(orig_addr_ptr, decomp_addr_ptr) 389 | { 390 | return Self::make_mismatch(&i1, &i2, mismatch_cause); 391 | } 392 | 393 | // If the data symbol reference matches, allow the instructions to be different. 394 | diff_ok = true; 395 | } 396 | 397 | if !diff_ok && i1.bytes() != i2.bytes() { 398 | return Self::make_mismatch(&i1, &i2, MismatchCause::Unknown); 399 | } 400 | 401 | state.forget_modified_registers(&detail.0); 402 | } 403 | 404 | // Anything else. 405 | _ => { 406 | if i1.bytes() != i2.bytes() { 407 | return Self::make_mismatch(&i1, &i2, MismatchCause::Unknown); 408 | } 409 | 410 | state.forget_modified_registers(&detail.0); 411 | } 412 | } 413 | } 414 | 415 | Ok(None) 416 | } 417 | 418 | /// Returns None on success and a MismatchCause on failure. 419 | fn check_function_call(&self, orig_addr: u64, decomp_addr: u64) -> Option { 420 | let info = *self.known_functions.get(&orig_addr)?; 421 | let name = info.name.as_str(); 422 | let decomp_symbol = self.decomp_symtab.get(name)?; 423 | let expected = decomp_symbol.st_value; 424 | 425 | if decomp_addr == expected { 426 | None 427 | } else { 428 | let actual_symbol_name = self.translate_decomp_addr_to_name(decomp_addr); 429 | 430 | Some(MismatchCause::FunctionCall(ReferenceDiff { 431 | referenced_symbol: orig_addr, 432 | expected_ref_in_decomp: expected, 433 | actual_ref_in_decomp: decomp_addr, 434 | expected_symbol_name: name.to_string(), 435 | actual_symbol_name: actual_symbol_name.unwrap_or("unknown").to_string(), 436 | })) 437 | } 438 | } 439 | 440 | /// Returns None on success and a MismatchCause on failure. 441 | fn check_data_symbol_ex( 442 | &self, 443 | orig_addr: u64, 444 | decomp_addr: u64, 445 | symbol: &DataSymbol, 446 | ) -> Option { 447 | let decomp_symbol = self.decomp_symtab.get(symbol.name.as_str())?; 448 | let expected = decomp_symbol.st_value; 449 | 450 | if decomp_addr == expected { 451 | None 452 | } else { 453 | let actual_symbol_name = self.translate_decomp_addr_to_name(decomp_addr); 454 | 455 | Some(MismatchCause::DataReference(ReferenceDiff { 456 | referenced_symbol: orig_addr, 457 | expected_ref_in_decomp: expected, 458 | actual_ref_in_decomp: decomp_addr, 459 | expected_symbol_name: symbol.name.to_string(), 460 | actual_symbol_name: actual_symbol_name.unwrap_or("unknown").to_string(), 461 | })) 462 | } 463 | } 464 | 465 | /// Returns None on success and a MismatchCause on failure. 466 | fn check_data_symbol(&self, orig_addr: u64, decomp_addr: u64) -> Option { 467 | let symbol = self.known_data_symbols.get_symbol(orig_addr)?; 468 | self.check_data_symbol_ex(orig_addr, decomp_addr, symbol) 469 | } 470 | 471 | /// Returns None on success and a MismatchCause on failure. 472 | /// Unlike check_data_symbol, this function takes the addresses of *pointers to* possible data symbols, 473 | /// not the symbols themselves. 474 | fn check_data_symbol_ptr( 475 | &self, 476 | orig_addr_ptr: u64, 477 | decomp_addr_ptr: u64, 478 | ) -> Option { 479 | if !elf::is_in_section(self.orig_got_section?, orig_addr_ptr, 8) { 480 | return None; 481 | } 482 | 483 | let orig_addr = u64::from_le_bytes( 484 | elf::get_elf_bytes(self.orig_elf, orig_addr_ptr, 8) 485 | .ok()? 486 | .try_into() 487 | .ok()?, 488 | ); 489 | 490 | let data_symbol = self.known_data_symbols.get_symbol(orig_addr)?; 491 | let decomp_addr = *self.decomp_glob_data_table.get(&decomp_addr_ptr)?; 492 | self.check_data_symbol_ex(orig_addr, decomp_addr, data_symbol) 493 | } 494 | 495 | fn make_mismatch( 496 | i1: &cs::Insn, 497 | i2: &cs::Insn, 498 | cause: MismatchCause, 499 | ) -> Result> { 500 | Ok(Some(Mismatch { 501 | addr_orig: i1.address(), 502 | addr_decomp: i2.address(), 503 | cause, 504 | })) 505 | } 506 | 507 | #[cold] 508 | #[inline(never)] 509 | fn translate_decomp_addr_to_name(&self, decomp_addr: u64) -> Option<&'decomp_elf str> { 510 | let map = self.decomp_addr_to_name_map.get_or_create(|| { 511 | let map = elf::make_addr_to_name_map(self.decomp_elf).ok(); 512 | map.unwrap_or_default() 513 | }); 514 | map.get(&decomp_addr).copied() 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /viking/src/elf.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | ffi::{c_char, CStr}, 4 | fs::File, 5 | ops::Range, 6 | path::Path, 7 | path::PathBuf, 8 | }; 9 | 10 | use anyhow::{anyhow, bail, Context, Result}; 11 | use goblin::{ 12 | container, 13 | elf::{ 14 | dynamic, reloc, section_header, sym, Dynamic, Elf, ProgramHeader, RelocSection, 15 | SectionHeader, Sym, Symtab, 16 | }, 17 | elf64::program_header::PT_LOAD, 18 | strtab::Strtab, 19 | }; 20 | use memmap::{Mmap, MmapOptions}; 21 | use owning_ref::OwningHandle; 22 | use rustc_hash::FxHashMap; 23 | 24 | use crate::repo; 25 | 26 | pub type OwnedElf = OwningHandle, Box>>; 27 | pub type SymbolTableByName<'a> = HashMap<&'a str, goblin::elf::Sym>; 28 | pub type SymbolTableByAddr = FxHashMap; 29 | pub type AddrToNameMap<'a> = FxHashMap; 30 | pub type GlobDataTable = FxHashMap; 31 | 32 | pub struct Function<'a> { 33 | /// The ELF this function comes from. 34 | pub owner_elf: &'a OwnedElf, 35 | /// The virtual address of the function in its containing executable. 36 | /// *Note*: does not contain the IDA base (0x7100000000). 37 | pub addr: u64, 38 | /// The bytes that make up the code for this function. 39 | pub code: &'a [u8], 40 | } 41 | 42 | impl<'a> Function<'a> { 43 | #[inline] 44 | pub fn get_addr_range(&self) -> Range { 45 | self.addr..(self.addr + self.code.len() as u64) 46 | } 47 | } 48 | 49 | #[inline] 50 | fn make_goblin_ctx() -> container::Ctx { 51 | // 64-bit, little endian 52 | container::Ctx::new(container::Container::Big, container::Endian::Little) 53 | } 54 | 55 | /// A stripped down version of `goblin::elf::Elf::parse`, parsing only the sections that we need. 56 | /// 57 | /// *Warning*: In particular, `strtab`, `dynstrtab`, `soname` and `libraries` are **not** parsed. 58 | fn parse_elf_faster(bytes: &[u8]) -> Result { 59 | let header = Elf::parse_header(bytes)?; 60 | let mut elf = Elf::lazy_parse(header)?; 61 | let ctx = make_goblin_ctx(); 62 | 63 | elf.program_headers = 64 | ProgramHeader::parse(bytes, header.e_phoff as usize, header.e_phnum as usize, ctx)?; 65 | 66 | elf.section_headers = 67 | SectionHeader::parse(bytes, header.e_shoff as usize, header.e_shnum as usize, ctx)?; 68 | 69 | let get_strtab = |section_headers: &[SectionHeader], section_idx: usize| { 70 | if section_idx >= section_headers.len() { 71 | Ok(Strtab::default()) 72 | } else { 73 | let shdr = §ion_headers[section_idx]; 74 | shdr.check_size(bytes.len())?; 75 | Strtab::parse(bytes, shdr.sh_offset as usize, shdr.sh_size as usize, 0x0) 76 | } 77 | }; 78 | 79 | let strtab_idx = header.e_shstrndx as usize; 80 | elf.shdr_strtab = get_strtab(&elf.section_headers, strtab_idx)?; 81 | 82 | for shdr in &elf.section_headers { 83 | if shdr.sh_type == section_header::SHT_SYMTAB { 84 | let size = shdr.sh_entsize; 85 | let count = if size == 0 { 0 } else { shdr.sh_size / size }; 86 | elf.syms = Symtab::parse(bytes, shdr.sh_offset as usize, count as usize, ctx)?; 87 | } 88 | } 89 | 90 | elf.dynamic = Dynamic::parse(bytes, &elf.program_headers, ctx)?; 91 | if let Some(ref dynamic) = elf.dynamic { 92 | let dyn_info = &dynamic.info; 93 | // parse the dynamic relocations 94 | elf.dynrelas = RelocSection::parse(bytes, dyn_info.rela, dyn_info.relasz, true, ctx)?; 95 | elf.dynrels = RelocSection::parse(bytes, dyn_info.rel, dyn_info.relsz, false, ctx)?; 96 | let is_rela = dyn_info.pltrel == dynamic::DT_RELA; 97 | elf.pltrelocs = 98 | RelocSection::parse(bytes, dyn_info.jmprel, dyn_info.pltrelsz, is_rela, ctx)?; 99 | } 100 | 101 | Ok(elf) 102 | } 103 | 104 | pub fn load_elf(path: &Path) -> Result { 105 | let file = File::open(path)?; 106 | let mmap = unsafe { MmapOptions::new().map(&file)? }; 107 | 108 | OwningHandle::try_new(Box::new((file, mmap)), |pair| unsafe { 109 | let elf = parse_elf_faster(&(*pair).1).context("failed to load ELF")?; 110 | Ok(Box::new(elf)) 111 | }) 112 | } 113 | 114 | pub fn load_orig_elf(version: Option<&str>) -> Result { 115 | load_elf(repo::get_data_path(version)?.join("main.elf").as_path()) 116 | } 117 | 118 | pub fn load_decomp_elf(version: Option<&str>) -> Result { 119 | let decomp_elf_path: PathBuf = repo::get_build_path(version) 120 | .expect("Failed to get build path") 121 | .join(&repo::get_config().build_target); 122 | 123 | load_elf(&decomp_elf_path) 124 | } 125 | 126 | pub struct SymbolStringTable<'elf> { 127 | bytes: &'elf [u8], 128 | } 129 | 130 | impl<'elf> SymbolStringTable<'elf> { 131 | pub fn from_elf(elf: &'elf OwnedElf) -> Result { 132 | let bytes = &*elf.as_owner().1; 133 | for shdr in &elf.section_headers { 134 | if shdr.sh_type == section_header::SHT_SYMTAB { 135 | let table_hdr = elf 136 | .section_headers 137 | .get(shdr.sh_link as usize) 138 | .ok_or_else(|| anyhow!("symbol string table index out of bounds"))?; 139 | 140 | table_hdr.check_size(bytes.len())?; 141 | 142 | let start = table_hdr.sh_offset as usize; 143 | let end = start + table_hdr.sh_size as usize; 144 | return Ok(SymbolStringTable { 145 | bytes: &bytes[start..end], 146 | }); 147 | } 148 | } 149 | bail!("couldn't find symbol string table") 150 | } 151 | 152 | pub fn get_string(&self, offset: usize) -> &'elf str { 153 | unsafe { 154 | std::str::from_utf8_unchecked( 155 | CStr::from_ptr(self.bytes[offset..self.bytes.len()].as_ptr() as *const c_char) 156 | .to_bytes(), 157 | ) 158 | } 159 | } 160 | } 161 | 162 | fn filter_out_useless_syms(sym: &Sym) -> bool { 163 | matches!( 164 | sym.st_type(), 165 | sym::STT_OBJECT | sym::STT_FUNC | sym::STT_COMMON | sym::STT_TLS 166 | ) 167 | } 168 | 169 | #[inline] 170 | pub fn is_undefined_sym(sym: &Sym) -> bool { 171 | sym.st_type() == sym::STT_NOTYPE && sym.st_value == 0 172 | } 173 | 174 | pub fn find_function_symbol_by_name(elf: &OwnedElf, name: &str) -> Result { 175 | let strtab = SymbolStringTable::from_elf(elf)?; 176 | 177 | for symbol in elf.syms.iter().filter(filter_out_useless_syms) { 178 | if name == strtab.get_string(symbol.st_name) { 179 | return Ok(symbol); 180 | } 181 | } 182 | bail!("unknown function") 183 | } 184 | 185 | pub fn make_symbol_map_by_name(elf: &OwnedElf) -> Result { 186 | let mut map = SymbolTableByName::with_capacity_and_hasher( 187 | elf.syms.iter().filter(filter_out_useless_syms).count(), 188 | Default::default(), 189 | ); 190 | 191 | let strtab = SymbolStringTable::from_elf(elf)?; 192 | 193 | for symbol in elf.syms.iter().filter(filter_out_useless_syms) { 194 | map.entry(strtab.get_string(symbol.st_name)) 195 | .or_insert(symbol); 196 | } 197 | Ok(map) 198 | } 199 | 200 | pub fn make_symbol_map_by_addr(elf: &OwnedElf) -> SymbolTableByAddr { 201 | let mut map = SymbolTableByAddr::with_capacity_and_hasher( 202 | elf.syms.iter().filter(filter_out_useless_syms).count(), 203 | Default::default(), 204 | ); 205 | for symbol in elf.syms.iter().filter(filter_out_useless_syms) { 206 | map.entry(symbol.st_value).or_insert(symbol); 207 | } 208 | map 209 | } 210 | 211 | pub fn make_addr_to_name_map(elf: &OwnedElf) -> Result { 212 | let mut map = AddrToNameMap::with_capacity_and_hasher( 213 | elf.syms.iter().filter(filter_out_useless_syms).count(), 214 | Default::default(), 215 | ); 216 | 217 | let strtab = SymbolStringTable::from_elf(elf)?; 218 | 219 | for symbol in elf.syms.iter().filter(filter_out_useless_syms) { 220 | map.entry(symbol.st_value) 221 | .or_insert_with(|| strtab.get_string(symbol.st_name)); 222 | } 223 | Ok(map) 224 | } 225 | 226 | fn parse_symtab<'a>(elf: &'a OwnedElf, shdr: &'a SectionHeader) -> Result> { 227 | let bytes = &elf.as_owner().1; 228 | let size = shdr.sh_entsize; 229 | let count = if size == 0 { 0 } else { shdr.sh_size / size }; 230 | 231 | let syms = Symtab::parse( 232 | bytes, 233 | shdr.sh_offset as usize, 234 | count as usize, 235 | make_goblin_ctx(), 236 | )?; 237 | Ok(syms) 238 | } 239 | 240 | pub fn find_section<'a>(elf: &'a OwnedElf, name: &str) -> Result<&'a SectionHeader> { 241 | elf.section_headers 242 | .iter() 243 | .find(|&header| &elf.shdr_strtab[header.sh_name] == name) 244 | .ok_or_else(|| anyhow!("failed to find {} section", name)) 245 | } 246 | 247 | pub fn get_linked_section<'a>( 248 | elf: &'a OwnedElf, 249 | shdr: &'a SectionHeader, 250 | ) -> Result<&'a SectionHeader> { 251 | elf.section_headers 252 | .get(shdr.sh_link as usize) 253 | .ok_or_else(|| anyhow!("could not get linked section")) 254 | } 255 | 256 | #[inline] 257 | pub fn is_in_section(section: &SectionHeader, addr: u64, size: u64) -> bool { 258 | let begin = section.sh_addr; 259 | let end = begin + section.sh_size; 260 | (begin..end).contains(&addr) && (begin..=end).contains(&(addr + size)) 261 | } 262 | 263 | pub fn build_glob_data_table(elf: &OwnedElf) -> Result { 264 | let section = &elf.dynrelas; 265 | let section_hdr = find_section(elf, ".rela.dyn")?; 266 | // The corresponding symbol table. 267 | let symtab = parse_symtab(elf, get_linked_section(elf, section_hdr)?)?; 268 | 269 | let mut table = GlobDataTable::with_capacity_and_hasher(section.len(), Default::default()); 270 | 271 | for reloc in section.iter() { 272 | let symbol_value: u64 = symtab 273 | .get(reloc.r_sym) 274 | .ok_or_else(|| anyhow!("invalid symbol index"))? 275 | .st_value; 276 | 277 | match reloc.r_type { 278 | reloc::R_AARCH64_GLOB_DAT => { 279 | table.insert( 280 | reloc.r_offset, 281 | (symbol_value as i64 + reloc.r_addend.unwrap()) as u64, 282 | ); 283 | } 284 | reloc::R_AARCH64_RELATIVE => { 285 | // FIXME: this should be Delta(S) + A. 286 | table.insert( 287 | reloc.r_offset, 288 | (symbol_value as i64 + reloc.r_addend.unwrap()) as u64, 289 | ); 290 | } 291 | _ => (), 292 | } 293 | } 294 | 295 | Ok(table) 296 | } 297 | 298 | pub fn get_offset_in_file(elf: &OwnedElf, addr: u64) -> Result { 299 | let addr = addr as usize; 300 | for segment in elf.program_headers.iter() { 301 | if segment.p_type != PT_LOAD { 302 | continue; 303 | } 304 | 305 | if segment.vm_range().contains(&addr) { 306 | return Ok(segment.file_range().start + addr - segment.vm_range().start); 307 | } 308 | } 309 | bail!("{:#x} doesn't belong to any segment", addr) 310 | } 311 | 312 | pub fn get_elf_bytes(elf: &OwnedElf, addr: u64, size: u64) -> Result<&[u8]> { 313 | let offset = get_offset_in_file(elf, addr)?; 314 | let size = size as usize; 315 | Ok(&elf.as_owner().1[offset..(offset + size)]) 316 | } 317 | 318 | pub fn get_function(elf: &OwnedElf, addr: u64, size: u64) -> Result { 319 | Ok(Function { 320 | owner_elf: elf, 321 | addr, 322 | code: get_elf_bytes(elf, addr, size)?, 323 | }) 324 | } 325 | 326 | pub fn get_function_by_name<'a>( 327 | elf: &'a OwnedElf, 328 | symbols: &SymbolTableByName, 329 | name: &str, 330 | ) -> Result> { 331 | let symbol = symbols 332 | .get(&name) 333 | .ok_or_else(|| anyhow!("unknown function: {}", name))?; 334 | get_function(elf, symbol.st_value, symbol.st_size) 335 | } 336 | -------------------------------------------------------------------------------- /viking/src/functions.rs: -------------------------------------------------------------------------------- 1 | use crate::repo; 2 | use anyhow::{bail, ensure, Context, Result}; 3 | use rayon::prelude::*; 4 | use rustc_hash::FxHashMap; 5 | use std::{ 6 | collections::HashSet, 7 | path::{Path, PathBuf}, 8 | }; 9 | 10 | #[derive(Clone, Debug, PartialEq, Eq)] 11 | pub enum Status { 12 | Matching, 13 | NonMatchingMinor, 14 | NonMatchingMajor, 15 | NotDecompiled, 16 | Wip, 17 | Library, 18 | } 19 | 20 | impl Status { 21 | pub fn description(&self) -> &'static str { 22 | match &self { 23 | Status::Matching => "matching", 24 | Status::NonMatchingMinor => "non-matching (minor)", 25 | Status::NonMatchingMajor => "non-matching (major)", 26 | Status::NotDecompiled => "not decompiled", 27 | Status::Wip => "WIP", 28 | Status::Library => "library function", 29 | } 30 | } 31 | } 32 | 33 | #[derive(Clone, Debug)] 34 | pub struct Info { 35 | pub addr: u64, 36 | pub size: u32, 37 | pub name: String, 38 | pub status: Status, 39 | } 40 | 41 | impl Info { 42 | pub fn is_decompiled(&self) -> bool { 43 | !matches!(self.status, Status::NotDecompiled | Status::Library) 44 | } 45 | 46 | pub fn get_start(&self) -> u64 { 47 | self.addr | ADDRESS_BASE 48 | } 49 | 50 | pub fn get_end(&self) -> u64 { 51 | self.get_start() + self.size as u64 52 | } 53 | } 54 | 55 | pub const CSV_HEADER: &[&str] = &["Address", "Quality", "Size", "Name"]; 56 | pub const ADDRESS_BASE: u64 = 0x71_0000_0000; 57 | 58 | fn parse_base_16(value: &str) -> Result { 59 | if let Some(stripped) = value.strip_prefix("0x") { 60 | Ok(u64::from_str_radix(stripped, 16)?) 61 | } else { 62 | Ok(u64::from_str_radix(value, 16)?) 63 | } 64 | } 65 | 66 | pub fn parse_address(value: &str) -> Result { 67 | Ok(parse_base_16(value)? - ADDRESS_BASE) 68 | } 69 | 70 | fn parse_function_csv_entry(record: &csv::StringRecord) -> Result { 71 | ensure!(record.len() == 4, "invalid record"); 72 | 73 | let addr = parse_address(&record[0])?; 74 | let status_code = record[1].chars().next(); 75 | let size = record[2].parse::()?; 76 | let decomp_name = record[3].to_string(); 77 | 78 | let status = match status_code { 79 | Some('m') => Status::NonMatchingMinor, 80 | Some('M') => Status::NonMatchingMajor, 81 | Some('O') => Status::Matching, 82 | Some('U') => Status::NotDecompiled, 83 | Some('W') => Status::Wip, 84 | Some('L') => Status::Library, 85 | Some(code) => bail!("unexpected status code: {}", code), 86 | None => bail!("missing status code"), 87 | }; 88 | 89 | Ok(Info { 90 | addr, 91 | size, 92 | name: decomp_name, 93 | status, 94 | }) 95 | } 96 | 97 | fn check_for_duplicate_names(functions: &[Info], num_names: usize) -> Result<()> { 98 | let mut known_names = HashSet::with_capacity(num_names); 99 | let mut duplicates = Vec::new(); 100 | 101 | for entry in functions { 102 | if entry.is_decompiled() && entry.name.is_empty() { 103 | bail!( 104 | "function at {:016x} is marked as O/M/m but has an empty name", 105 | entry.get_start() 106 | ); 107 | } 108 | 109 | if !entry.name.is_empty() && !known_names.insert(&entry.name) { 110 | duplicates.push(&entry.name); 111 | } 112 | } 113 | 114 | if !duplicates.is_empty() { 115 | bail!("found duplicates: {:#?}", duplicates); 116 | } 117 | 118 | Ok(()) 119 | } 120 | 121 | fn check_for_overlapping_functions(functions: &[Info]) -> Result<()> { 122 | for pair in functions.windows(2) { 123 | let first = &pair[0]; 124 | let second = &pair[1]; 125 | 126 | ensure!( 127 | first.get_start() < second.get_start() && first.get_end() <= second.get_start(), 128 | "overlapping functions: {:016x} - {:016x} and {:016x} - {:016x}", 129 | first.get_start(), 130 | first.get_end(), 131 | second.get_start(), 132 | second.get_end(), 133 | ); 134 | } 135 | 136 | Ok(()) 137 | } 138 | 139 | /// Returns a Vec of all functions that are listed in the specified CSV. 140 | pub fn get_functions_for_path(csv_path: &Path) -> Result> { 141 | let mut reader = csv::ReaderBuilder::new() 142 | .has_headers(false) 143 | .quoting(false) 144 | .from_path(csv_path)?; 145 | 146 | // We build the result array manually without using csv iterators for performance reasons. 147 | let mut result = Vec::with_capacity(110_000); 148 | let mut record = csv::StringRecord::new(); 149 | let mut line_number = 1; 150 | let mut num_names = 0; 151 | if reader.read_record(&mut record)? { 152 | // Verify that the CSV has the correct format. 153 | ensure!(record.len() == 4, "invalid record; expected 4 fields"); 154 | ensure!(record == *CSV_HEADER, 155 | "wrong CSV format; this program only works with the new function list format (added in commit 1d4c815fbae3)" 156 | ); 157 | line_number += 1; 158 | } 159 | 160 | while reader.read_record(&mut record)? { 161 | let entry = parse_function_csv_entry(&record) 162 | .with_context(|| format!("failed to parse CSV record at line {}", line_number))?; 163 | 164 | if !entry.name.is_empty() { 165 | num_names += 1; 166 | } 167 | 168 | result.push(entry); 169 | line_number += 1; 170 | } 171 | 172 | check_for_duplicate_names(&result, num_names)?; 173 | check_for_overlapping_functions(&result)?; 174 | 175 | Ok(result) 176 | } 177 | 178 | pub fn write_functions_to_path(csv_path: &Path, functions: &[Info]) -> Result<()> { 179 | let mut writer = csv::Writer::from_path(csv_path)?; 180 | writer.write_record(CSV_HEADER)?; 181 | 182 | for function in functions { 183 | let addr = format!("0x{:016x}", function.get_start()); 184 | let status = match function.status { 185 | Status::Matching => "O", 186 | Status::NonMatchingMinor => "m", 187 | Status::NonMatchingMajor => "M", 188 | Status::NotDecompiled => "U", 189 | Status::Wip => "W", 190 | Status::Library => "L", 191 | } 192 | .to_string(); 193 | let size = format!("{:06}", function.size); 194 | let name = function.name.clone(); 195 | writer.write_record(&[addr, status, size, name])?; 196 | } 197 | 198 | Ok(()) 199 | } 200 | 201 | pub fn get_functions_csv_path(version: Option<&str>) -> PathBuf { 202 | let mut path = repo::get_repo_root().expect("Failed to get repo root"); 203 | let config_functions_csv = repo::get_config().functions_csv.clone(); 204 | let functions_csv = version 205 | .map(|s| config_functions_csv.replace("{version}", s)) 206 | .unwrap_or(config_functions_csv); 207 | path.push(functions_csv); 208 | 209 | path 210 | } 211 | 212 | /// Returns a Vec of all known functions in the executable. 213 | pub fn get_functions(version: Option<&str>) -> Result> { 214 | get_functions_for_path(get_functions_csv_path(version).as_path()) 215 | } 216 | 217 | pub fn write_functions(functions: &[Info], version: Option<&str>) -> Result<()> { 218 | write_functions_to_path(get_functions_csv_path(version).as_path(), functions) 219 | } 220 | 221 | pub fn make_known_function_map(functions: &[Info]) -> FxHashMap { 222 | let mut known_functions = 223 | FxHashMap::with_capacity_and_hasher(functions.len(), Default::default()); 224 | 225 | for function in functions { 226 | if function.name.is_empty() { 227 | continue; 228 | } 229 | known_functions.insert(function.addr, function); 230 | } 231 | 232 | known_functions 233 | } 234 | 235 | pub fn make_known_function_name_map(functions: &[Info]) -> FxHashMap<&str, &Info> { 236 | let mut known_functions = 237 | FxHashMap::with_capacity_and_hasher(functions.len(), Default::default()); 238 | 239 | for function in functions { 240 | if function.name.is_empty() { 241 | continue; 242 | } 243 | known_functions.insert(function.name.as_str(), function); 244 | } 245 | 246 | known_functions 247 | } 248 | 249 | /// Demangle a C++ symbol. 250 | pub fn demangle_str(name: &str) -> Result { 251 | if !name.starts_with("_Z") { 252 | bail!("not an external mangled name"); 253 | } 254 | 255 | let symbol = cpp_demangle::Symbol::new(name)?; 256 | let options = cpp_demangle::DemangleOptions::new(); 257 | Ok(symbol.demangle(&options)?) 258 | } 259 | 260 | pub fn fuzzy_search<'a>(functions: &'a [Info], name: &str) -> Vec<&'a Info> { 261 | let exact_match = functions 262 | .par_iter() 263 | .find_first(|function| function.name == name); 264 | 265 | if let Some(exact_match) = exact_match { 266 | return vec![exact_match]; 267 | } 268 | 269 | // Find all functions whose demangled name contains the specified string. 270 | // This is more expensive than a simple string comparison, so only do this after 271 | // we have failed to find an exact match. 272 | let mut candidates: Vec<&Info> = functions 273 | .par_iter() 274 | .filter(|function| { 275 | demangle_str(&function.name).map_or(false, |demangled| demangled.contains(name)) 276 | || function.name.contains(name) 277 | }) 278 | .collect(); 279 | 280 | candidates.sort_by_key(|info| info.addr); 281 | candidates 282 | } 283 | -------------------------------------------------------------------------------- /viking/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod capstone_utils; 2 | pub mod checks; 3 | pub mod elf; 4 | pub mod functions; 5 | pub mod repo; 6 | pub mod ui; 7 | -------------------------------------------------------------------------------- /viking/src/repo.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use lazy_static::lazy_static; 3 | use std::path::PathBuf; 4 | 5 | #[derive(serde::Deserialize)] 6 | pub struct Config { 7 | pub build_target: String, 8 | pub functions_csv: String, 9 | pub default_version: Option, 10 | pub decomp_me: Option, 11 | } 12 | 13 | #[derive(serde::Deserialize)] 14 | pub struct ConfigDecompMe { 15 | pub compiler_name: String, 16 | 17 | /// Compilation flags that are used for creating scratches. 18 | /// Can be overridden using a compilation database. 19 | pub default_compile_flags: String, 20 | } 21 | 22 | lazy_static! { 23 | static ref CONFIG: Config = { 24 | let toml_path = get_repo_root() 25 | .expect("failed to get repo root") 26 | .join("tools/config.toml"); 27 | let raw = std::fs::read_to_string(toml_path.as_path()).expect("failed to read config file"); 28 | toml::from_str(&raw).expect("failed to parse config file") 29 | }; 30 | } 31 | 32 | pub fn get_config() -> &'static Config { 33 | &CONFIG 34 | } 35 | 36 | pub fn get_repo_root() -> Result { 37 | let current_dir = std::env::current_dir()?; 38 | let mut dir = current_dir.as_path(); 39 | 40 | loop { 41 | if ["data", "src"].iter().all(|name| dir.join(name).is_dir()) { 42 | return Ok(dir.to_path_buf()); 43 | } 44 | 45 | match dir.parent() { 46 | None => { 47 | bail!("failed to find repo root -- run this program inside the repo"); 48 | } 49 | Some(parent) => dir = parent, 50 | }; 51 | } 52 | } 53 | 54 | pub fn get_tools_path() -> Result { 55 | Ok(get_repo_root()?.join("tools/common")) 56 | } 57 | 58 | fn get_version_specific_dir_path(dir_name: &str, version: Option<&str>) -> Result { 59 | let dir_name = if let Some(v) = version { 60 | format!("{}/{}", dir_name, v) 61 | } else { 62 | dir_name.to_string() 63 | }; 64 | 65 | Ok(get_repo_root()?.join(dir_name)) 66 | } 67 | 68 | pub fn get_data_path(version: Option<&str>) -> Result { 69 | get_version_specific_dir_path("data", version) 70 | } 71 | 72 | pub fn get_build_path(version: Option<&str>) -> Result { 73 | get_version_specific_dir_path("build", version) 74 | } 75 | -------------------------------------------------------------------------------- /viking/src/tools/check.rs: -------------------------------------------------------------------------------- 1 | use anyhow::bail; 2 | use anyhow::Context; 3 | use anyhow::Result; 4 | use capstone as cs; 5 | use capstone::arch::BuildsCapstone; 6 | use colored::*; 7 | use goblin::elf::sym::STT_FUNC; 8 | use itertools::Itertools; 9 | use lexopt::prelude::*; 10 | use rayon::prelude::*; 11 | use std::cell::RefCell; 12 | use std::collections::HashMap; 13 | use std::collections::HashSet; 14 | use std::sync::atomic; 15 | use std::sync::Mutex; 16 | use viking::checks::FunctionChecker; 17 | use viking::checks::Mismatch; 18 | use viking::elf; 19 | use viking::functions; 20 | use viking::functions::Status; 21 | use viking::repo; 22 | use viking::ui; 23 | 24 | use mimalloc::MiMalloc; 25 | 26 | #[global_allocator] 27 | static GLOBAL: MiMalloc = MiMalloc; 28 | 29 | #[derive(Default)] 30 | struct Args { 31 | function: Option, 32 | version: Option, 33 | always_diff: bool, 34 | warnings_as_errors: bool, 35 | print_help: bool, 36 | other_args: Vec, 37 | } 38 | 39 | impl Args { 40 | fn get_version(&self) -> Option<&str> { 41 | self.version.as_deref() 42 | } 43 | } 44 | 45 | fn main() -> Result<()> { 46 | ui::init_prompt_settings(); 47 | 48 | let args = parse_args()?; 49 | 50 | if args.print_help { 51 | print_help()?; 52 | return Ok(()); 53 | } 54 | 55 | let version = args.get_version(); 56 | 57 | let orig_elf = elf::load_orig_elf(version).context("failed to load original ELF")?; 58 | let decomp_elf = elf::load_decomp_elf(version).context("failed to load decomp ELF")?; 59 | 60 | // Load these in parallel. 61 | let mut decomp_symtab = None; 62 | let mut decomp_glob_data_table = None; 63 | let mut functions = None; 64 | 65 | rayon::scope(|s| { 66 | s.spawn(|_| decomp_symtab = Some(elf::make_symbol_map_by_name(&decomp_elf))); 67 | s.spawn(|_| decomp_glob_data_table = Some(elf::build_glob_data_table(&decomp_elf))); 68 | s.spawn(|_| functions = Some(functions::get_functions(version))); 69 | }); 70 | 71 | let decomp_symtab = decomp_symtab 72 | .unwrap() 73 | .context("failed to make symbol map")?; 74 | 75 | let decomp_glob_data_table = decomp_glob_data_table 76 | .unwrap() 77 | .context("failed to make global data table")?; 78 | 79 | let functions = functions.unwrap().context("failed to load function CSV")?; 80 | 81 | let checker = FunctionChecker::new( 82 | &orig_elf, 83 | &decomp_elf, 84 | &decomp_symtab, 85 | decomp_glob_data_table, 86 | &functions, 87 | version, 88 | ) 89 | .context("failed to construct FunctionChecker")?; 90 | 91 | if let Some(func) = &args.function { 92 | check_single(&checker, &functions, func, &args)?; 93 | } else { 94 | check_all(&checker, &functions, &args)?; 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | fn parse_args() -> Result { 101 | let mut args = Args { 102 | version: repo::get_config().default_version.clone(), 103 | ..Default::default() 104 | }; 105 | 106 | let mut parser = lexopt::Parser::from_env(); 107 | while let Some(arg) = parser.next()? { 108 | match arg { 109 | Long("version") => { 110 | args.version = Some(parser.value()?.into_string()?); 111 | } 112 | Long("always-diff") => { 113 | args.always_diff = true; 114 | } 115 | Long("warnings-as-errors") => { 116 | args.warnings_as_errors = true; 117 | } 118 | 119 | Long("help") | Short('h') => { 120 | args.print_help = true; 121 | } 122 | 123 | Value(other_val) if args.function.is_none() => { 124 | args.function = Some(other_val.into_string()?); 125 | } 126 | Value(other_val) if args.function.is_some() => { 127 | args.other_args.push(other_val.into_string()?); 128 | } 129 | Long(other_long) => { 130 | args.other_args.push(format!("--{}", other_long)); 131 | let opt = parser.optional_value(); 132 | if let Some(o) = opt { 133 | args.other_args.push(o.into_string()?); 134 | } 135 | } 136 | Short(other_short) => { 137 | args.other_args.push(format!("-{}", other_short)); 138 | } 139 | 140 | _ => return Err(arg.unexpected()), 141 | } 142 | } 143 | 144 | Ok(args) 145 | } 146 | 147 | fn print_help() -> Result<()> { 148 | println!( 149 | "Usage: check [function name] [--version VERSION] [--always-diff] [asm-differ arguments] 150 | 151 | Checks if the compiled bytecode of a function matches the assembly found within the game elf. If not, show the differences between them. 152 | If no function name is provided, all functions within the repository function list will be checked. 153 | 154 | optional arguments: 155 | 156 | -h, --help Show this help message and exit 157 | --version VERSION Check the function against version VERSION of the game elf 158 | --always-diff Show an assembly diff, even if the function matches 159 | All further arguments are forwarded onto asm-differ. 160 | 161 | asm-differ arguments:" 162 | ); 163 | 164 | let differ_path = repo::get_tools_path()?.join("asm-differ").join("diff.py"); 165 | 166 | // By default, invoking asm-differ using std::process:Process doesn't seem to allow argparse 167 | // (the python module asm-differ uses to print its help text) to correctly determine the number of columns in the host terminal. 168 | // To work around this, we'll detect that for it, and set it manually via the COLUMNS environment variable 169 | let num_columns = match crossterm::terminal::size() { 170 | Ok((num_columns, _num_rows)) => num_columns, 171 | Err(_) => 240, 172 | }; 173 | 174 | let output = std::process::Command::new(&differ_path) 175 | .current_dir(repo::get_tools_path()?) 176 | .arg("--help") 177 | .env("COLUMNS", num_columns.to_string()) 178 | .output() 179 | .with_context(|| format!("failed to launch asm-differ: {:?}", &differ_path))?; 180 | 181 | let asm_differ_help = String::from_utf8_lossy(&output.stdout); 182 | 183 | let asm_differ_arguments = asm_differ_help 184 | .split("optional arguments:") 185 | .collect::>() 186 | .get(1) 187 | .copied() 188 | .or_else(|| { 189 | asm_differ_help 190 | .split("options:") 191 | .collect::>() 192 | .get(1) 193 | .copied() 194 | }) 195 | .unwrap_or(&asm_differ_help); 196 | 197 | println!("{}", asm_differ_arguments); 198 | 199 | Ok(()) 200 | } 201 | 202 | enum CheckResult { 203 | // If a function does not match, but is marked as such, return this error to show an appropriate exit message. 204 | MismatchError, 205 | // If a function does match, but is marked as mismatching, return this warning to indicate this and fix its status. 206 | MatchWarn, 207 | // If a function does not match, but is marked as "not decompiled", return this warning to indicate this and fix its status. 208 | MismatchWarn, 209 | // Check result matches the expected value listed in the function table. 210 | Ok, 211 | } 212 | 213 | fn check_function( 214 | checker: &FunctionChecker, 215 | cs: &mut capstone::Capstone, 216 | function: &functions::Info, 217 | args: &Args, 218 | ) -> Result { 219 | let name = function.name.as_str(); 220 | let decomp_fn = elf::get_function_by_name(checker.decomp_elf, checker.decomp_symtab, name); 221 | 222 | match function.status { 223 | Status::NotDecompiled if decomp_fn.is_err() => return Ok(CheckResult::Ok), 224 | Status::Library => return Ok(CheckResult::Ok), 225 | _ => (), 226 | } 227 | 228 | if let Err(error) = decomp_fn { 229 | ui::print_warning(&format!( 230 | "couldn't check {}: {}", 231 | ui::format_symbol_name(name), 232 | error.to_string().dimmed(), 233 | )); 234 | if args.warnings_as_errors { 235 | return Err(error); 236 | } 237 | return Ok(CheckResult::Ok); 238 | } 239 | 240 | let decomp_fn = decomp_fn.unwrap(); 241 | 242 | let get_orig_fn = || { 243 | elf::get_function(checker.orig_elf, function.addr, function.size as u64).with_context( 244 | || { 245 | format!( 246 | "failed to get function {} ({}) from the original executable", 247 | name, 248 | ui::format_address(function.addr), 249 | ) 250 | }, 251 | ) 252 | }; 253 | 254 | match function.status { 255 | Status::Matching => { 256 | let orig_fn = get_orig_fn()?; 257 | 258 | let result = checker 259 | .check(cs, &orig_fn, &decomp_fn) 260 | .with_context(|| format!("checking {}", name))?; 261 | 262 | if let Some(mismatch) = result { 263 | let stderr = std::io::stderr(); 264 | let mut lock = stderr.lock(); 265 | ui::print_error_ex( 266 | &mut lock, 267 | &format!( 268 | "function {} is marked as matching but does not match", 269 | ui::format_symbol_name(name), 270 | ), 271 | ); 272 | ui::print_detail_ex(&mut lock, &format!("{}", mismatch)); 273 | return Ok(CheckResult::MismatchError); 274 | } 275 | } 276 | 277 | Status::NotDecompiled 278 | | Status::NonMatchingMinor 279 | | Status::NonMatchingMajor 280 | | Status::Wip => { 281 | let orig_fn = get_orig_fn()?; 282 | 283 | let result = checker 284 | .check(cs, &orig_fn, &decomp_fn) 285 | .with_context(|| format!("checking {}", name))?; 286 | 287 | if result.is_none() { 288 | ui::print_note(&format!( 289 | "function {} is marked as {} but matches", 290 | ui::format_symbol_name(name), 291 | function.status.description(), 292 | )); 293 | return Ok(CheckResult::MatchWarn); 294 | } else if function.status == Status::NotDecompiled { 295 | ui::print_note(&format!( 296 | "function {} is marked as {} but mismatches", 297 | ui::format_symbol_name(name), 298 | function.status.description(), 299 | )); 300 | return Ok(CheckResult::MismatchWarn); 301 | } 302 | } 303 | 304 | Status::Library => unreachable!(), 305 | }; 306 | 307 | Ok(CheckResult::Ok) 308 | } 309 | 310 | fn check_single( 311 | checker: &FunctionChecker, 312 | functions: &[functions::Info], 313 | fn_to_check: &str, 314 | args: &Args, 315 | ) -> Result<()> { 316 | let version = args.get_version(); 317 | let function = ui::fuzzy_search_function_interactively(functions, fn_to_check)?; 318 | let name = function.name.as_str(); 319 | 320 | eprintln!("{}", ui::format_symbol_name(name).bold()); 321 | 322 | if matches!(function.status, Status::Library) { 323 | bail!("L functions should not be decompiled"); 324 | } 325 | 326 | let resolved_name; 327 | let name = if checker.decomp_symtab.contains_key(name) { 328 | name 329 | } else { 330 | resolved_name = resolve_unknown_fn_interactively(name, checker.decomp_symtab, functions)?; 331 | &resolved_name 332 | }; 333 | 334 | let decomp_fn = elf::get_function_by_name(checker.decomp_elf, checker.decomp_symtab, name) 335 | .with_context(|| { 336 | format!( 337 | "failed to get decomp function: {}", 338 | ui::format_symbol_name(name) 339 | ) 340 | })?; 341 | 342 | let orig_fn = elf::get_function(checker.orig_elf, function.addr, function.size as u64)?; 343 | 344 | let mut maybe_mismatch = checker 345 | .check(&mut make_cs()?, &orig_fn, &decomp_fn) 346 | .with_context(|| format!("checking {}", name))?; 347 | 348 | let mut should_show_diff = args.always_diff; 349 | 350 | if let Some(mismatch) = &maybe_mismatch { 351 | eprintln!("{}\n{}", "mismatch".red().bold(), &mismatch); 352 | should_show_diff = true; 353 | } else { 354 | eprintln!("{}", "OK".green().bold()); 355 | } 356 | 357 | if should_show_diff { 358 | show_asm_differ(function, name, &args.other_args, version)?; 359 | 360 | maybe_mismatch = 361 | rediff_function_after_differ(functions, &orig_fn, name, &maybe_mismatch, version) 362 | .context("failed to rediff")?; 363 | } 364 | 365 | let new_status = match maybe_mismatch { 366 | None => Status::Matching, 367 | _ if function.status == Status::NotDecompiled => Status::Wip, 368 | _ => function.status.clone(), 369 | }; 370 | 371 | // Update the function entry if needed. 372 | let status_changed = function.status != new_status; 373 | let name_was_ambiguous = function.name != name; 374 | if status_changed || name_was_ambiguous { 375 | if status_changed { 376 | ui::print_note(&format!( 377 | "changing status from {:?} to {:?}", 378 | function.status, new_status 379 | )); 380 | } 381 | 382 | update_function_in_function_list(functions, function.addr, version, |entry| { 383 | entry.status = new_status.clone(); 384 | entry.name = name.to_string(); 385 | })?; 386 | } 387 | 388 | Ok(()) 389 | } 390 | 391 | fn check_all(checker: &FunctionChecker, functions: &[functions::Info], args: &Args) -> Result<()> { 392 | let failed = atomic::AtomicBool::new(false); 393 | let new_function_statuses: Mutex> = Mutex::new(HashMap::new()); 394 | 395 | functions.par_iter().for_each(|function| { 396 | let result = CAPSTONE.with(|cs| -> Result<()> { 397 | let mut cs = cs.borrow_mut(); 398 | let ok = check_function(checker, &mut cs, function, args)?; 399 | match ok { 400 | CheckResult::MismatchError => { 401 | failed.store(true, atomic::Ordering::Relaxed); 402 | } 403 | CheckResult::MatchWarn => { 404 | new_function_statuses 405 | .lock() 406 | .unwrap() 407 | .insert(function.addr, functions::Status::Matching); 408 | } 409 | CheckResult::MismatchWarn => { 410 | new_function_statuses 411 | .lock() 412 | .unwrap() 413 | .insert(function.addr, functions::Status::NonMatchingMajor); 414 | } 415 | CheckResult::Ok => {} 416 | } 417 | Ok(()) 418 | }); 419 | 420 | if result.is_err() { 421 | failed.store(true, atomic::Ordering::Relaxed); 422 | } 423 | }); 424 | 425 | update_function_statuses( 426 | functions, 427 | &new_function_statuses.lock().unwrap(), 428 | args.version.as_deref(), 429 | ) 430 | .with_context(|| "failed to update function statuses")?; 431 | 432 | if failed.load(atomic::Ordering::Relaxed) { 433 | bail!("found at least one error"); 434 | } else { 435 | eprintln!("{}", "OK".green().bold()); 436 | Ok(()) 437 | } 438 | } 439 | 440 | #[cold] 441 | #[inline(never)] 442 | fn make_cs() -> Result { 443 | cs::Capstone::new() 444 | .arm64() 445 | .mode(cs::arch::arm64::ArchMode::Arm) 446 | .detail(true) 447 | .build() 448 | .or_else(viking::capstone_utils::translate_cs_error) 449 | } 450 | 451 | thread_local! { 452 | static CAPSTONE: RefCell = RefCell::new(make_cs().unwrap()); 453 | } 454 | 455 | fn update_function_statuses( 456 | functions: &[functions::Info], 457 | new_function_statuses: &HashMap, 458 | version: Option<&str>, 459 | ) -> Result<()> { 460 | if new_function_statuses.is_empty() { 461 | return Ok(()); 462 | } 463 | 464 | let mut new_functions = functions.to_vec(); 465 | 466 | new_functions.par_iter_mut().for_each(|info| { 467 | if let Some(status) = new_function_statuses.get(&info.addr) { 468 | info.status = status.clone() 469 | } 470 | }); 471 | 472 | functions::write_functions(&new_functions, version) 473 | } 474 | 475 | fn update_function_in_function_list( 476 | functions: &[functions::Info], 477 | addr: u64, 478 | version: Option<&str>, 479 | update_fn: UpdateFn, 480 | ) -> Result<()> 481 | where 482 | UpdateFn: FnOnce(&mut functions::Info), 483 | { 484 | let mut new_functions = functions.to_vec(); 485 | let entry = new_functions 486 | .iter_mut() 487 | .find(|info| info.addr == addr) 488 | .unwrap(); 489 | update_fn(entry); 490 | functions::write_functions(&new_functions, version) 491 | } 492 | 493 | fn resolve_unknown_fn_interactively( 494 | ambiguous_name: &str, 495 | decomp_symtab: &elf::SymbolTableByName, 496 | functions: &[functions::Info], 497 | ) -> Result { 498 | let fail = || -> Result { 499 | bail!("unknown function: {}", ambiguous_name); 500 | }; 501 | 502 | let mut candidates: Vec<_> = decomp_symtab 503 | .par_iter() 504 | .filter(|(&name, &sym)| { 505 | sym.st_type() == STT_FUNC 506 | && functions::demangle_str(name) 507 | .unwrap_or_else(|_| "".to_string()) 508 | .contains(ambiguous_name) 509 | }) 510 | .collect(); 511 | 512 | // Sort candidates by their name, then deduplicate them based on the address. 513 | // This ensures that e.g. C1 symbols take precedence over C2 symbols (if both are present). 514 | candidates.sort_by_key(|(&name, &sym)| (name, sym.st_value)); 515 | candidates.dedup_by_key(|(_, &sym)| sym.st_value); 516 | 517 | // Build a set of functions that have already been decompiled and listed, 518 | // so we don't suggest them to the user again. 519 | let decompiled_functions: HashSet<&str> = functions 520 | .iter() 521 | .filter(|info| info.is_decompiled()) 522 | .map(|info| info.name.as_str()) 523 | .collect(); 524 | candidates.retain(|(&name, _)| !decompiled_functions.contains(name)); 525 | 526 | if candidates.is_empty() { 527 | return fail(); 528 | } 529 | 530 | ui::clear_terminal(); 531 | 532 | if candidates.len() == 1 { 533 | let prompt = format!( 534 | "{} is ambiguous; did you mean: {}", 535 | ambiguous_name, 536 | ui::format_symbol_name(candidates[0].0), 537 | ); 538 | 539 | let confirmed = inquire::Confirm::new(&prompt).with_default(true).prompt()?; 540 | 541 | if !confirmed { 542 | return fail(); 543 | } 544 | 545 | Ok(candidates[0].0.to_string()) 546 | } else { 547 | let prompt = format!("{} is ambiguous; did you mean:", ambiguous_name); 548 | let options = candidates 549 | .iter() 550 | .map(|(&name, _)| ui::format_symbol_name(name)) 551 | .collect_vec(); 552 | 553 | let selection = inquire::Select::new(&prompt, options) 554 | .with_starting_cursor(0) 555 | .raw_prompt()? 556 | .index; 557 | 558 | Ok(candidates[selection].0.to_string()) 559 | } 560 | } 561 | 562 | fn show_asm_differ( 563 | function: &functions::Info, 564 | name: &str, 565 | differ_args: &[String], 566 | version: Option<&str>, 567 | ) -> Result<()> { 568 | let differ_path = repo::get_tools_path()?.join("asm-differ").join("diff.py"); 569 | let mut cmd = std::process::Command::new(&differ_path); 570 | 571 | cmd.current_dir(repo::get_tools_path()?) 572 | .arg("-I") 573 | .arg("-e") 574 | .arg(name) 575 | .arg(format!("0x{:016x}", function.addr)) 576 | .arg(format!("0x{:016x}", function.addr + function.size as u64)) 577 | .args(differ_args); 578 | 579 | if let Some(version) = version { 580 | cmd.args(["--version", version]); 581 | } 582 | 583 | cmd.status() 584 | .with_context(|| format!("failed to launch asm-differ: {:?}", &differ_path))?; 585 | 586 | Ok(()) 587 | } 588 | 589 | fn rediff_function_after_differ( 590 | functions: &[functions::Info], 591 | orig_fn: &elf::Function, 592 | name: &str, 593 | previous_check_result: &Option, 594 | version: Option<&str>, 595 | ) -> Result> { 596 | // Reload the decomp ELF because it may have been modified. 597 | // 598 | // This can typically happen if the differ was invoked with -mw (auto rebuild); 599 | // the user could have managed to match a function that used to be non-matching 600 | // back when the differ was launched. 601 | let decomp_elf = elf::load_decomp_elf(version).context("failed to reload decomp ELF")?; 602 | 603 | // Also reload the symbol table from the new ELF. 604 | let decomp_symtab = elf::make_symbol_map_by_name(&decomp_elf)?; 605 | let decomp_glob_data_table = elf::build_glob_data_table(&decomp_elf)?; 606 | 607 | // And grab the possibly updated function code. 608 | // Note that the original function doesn't need to be reloaded. 609 | let decomp_fn = 610 | elf::get_function_by_name(&decomp_elf, &decomp_symtab, name).with_context(|| { 611 | format!( 612 | "failed to reload decomp function: {}", 613 | ui::format_symbol_name(name) 614 | ) 615 | })?; 616 | 617 | // Invoke the checker again. 618 | let checker = FunctionChecker::new( 619 | orig_fn.owner_elf, 620 | &decomp_elf, 621 | &decomp_symtab, 622 | decomp_glob_data_table, 623 | functions, 624 | version, 625 | )?; 626 | 627 | let maybe_mismatch = checker 628 | .check(&mut make_cs()?, orig_fn, &decomp_fn) 629 | .with_context(|| format!("re-checking {}", name))?; 630 | 631 | if previous_check_result.is_some() == maybe_mismatch.is_some() { 632 | if let Some(mismatch) = &maybe_mismatch { 633 | eprintln!("{}\n{}", "still mismatching".red().bold(), &mismatch); 634 | } else { 635 | eprintln!("{}", "still OK".green().bold()); 636 | } 637 | } else { 638 | // Matching status has changed. 639 | if let Some(mismatch) = &maybe_mismatch { 640 | eprintln!("{}\n{}", "mismatching now".red().bold(), &mismatch); 641 | } else { 642 | eprintln!("{}", "OK now".green().bold()); 643 | } 644 | } 645 | 646 | Ok(maybe_mismatch) 647 | } 648 | -------------------------------------------------------------------------------- /viking/src/tools/decompme.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use addr2line::fallible_iterator::FallibleIterator; 4 | use anyhow::bail; 5 | use anyhow::Context; 6 | use anyhow::Result; 7 | use argh::FromArgs; 8 | use colored::Colorize; 9 | use viking::elf; 10 | use viking::functions; 11 | use viking::repo; 12 | use viking::ui; 13 | 14 | #[derive(FromArgs)] 15 | /// Upload a function to decomp.me. 16 | struct Args { 17 | /// version 18 | #[argh(option)] 19 | version: Option, 20 | 21 | /// decomp.me API endpoint base 22 | #[argh(option, default = r#""https://decomp.me".to_string()"#)] 23 | decomp_me_api: String, 24 | 25 | /// path to compilation database 26 | #[argh(option, short = 'p')] 27 | compilation_database: Option, 28 | 29 | /// source file to preprocess and upload 30 | #[argh(option, short = 's')] 31 | source_file: Option, 32 | 33 | /// name of the function to upload 34 | #[argh(positional)] 35 | function_name: String, 36 | } 37 | 38 | fn load_compilation_database(args: &Args) -> Result { 39 | let mut path; 40 | if let Some(p) = args.compilation_database.as_ref() { 41 | path = PathBuf::from(p.to_string()); 42 | } else { 43 | path = repo::get_build_path(args.version.as_deref())?; 44 | } 45 | 46 | if path.is_dir() { 47 | path.push("compile_commands.json"); 48 | } 49 | 50 | // Read the whole file at once and parse the JSON manually. 51 | // json_compilation_db::from_file is terribly slow. 52 | let data = std::fs::read(&path).context("failed to read compilation database")?; 53 | 54 | serde_json::from_slice(&data).context("failed to parse compilation database") 55 | } 56 | 57 | struct TranslationUnit { 58 | command: Vec, 59 | contents: String, 60 | path: String, 61 | } 62 | 63 | fn remove_c_and_output_flags(command: &mut Vec) { 64 | let mut remove_next = false; 65 | command.retain(|arg| { 66 | if remove_next { 67 | remove_next = false; 68 | return false; 69 | } 70 | if arg == "-c" { 71 | return false; 72 | } 73 | if arg == "-o" { 74 | remove_next = true; 75 | return false; 76 | } 77 | true 78 | }); 79 | } 80 | 81 | fn get_include_paths(stderr: &str) -> Vec<&str> { 82 | stderr 83 | .lines() 84 | .skip_while(|&line| line != "#include <...> search starts here:") 85 | .take_while(|&line| line != "End of search list.") 86 | .map(|line| line.split_whitespace().next().unwrap()) 87 | .filter(|&path| std::path::Path::new(path).is_dir()) 88 | .collect() 89 | } 90 | 91 | fn uninclude_system_includes<'a>(stdout: &'a str, include_paths: &Vec<&str>) -> String { 92 | let mut result = String::with_capacity(stdout.len()); 93 | 94 | // The current include stack. 95 | struct Include<'a> { 96 | file: &'a str, 97 | is_system_header: bool, 98 | } 99 | let mut include_stack: Vec = Vec::new(); 100 | 101 | let is_including_system_header = |include_stack: &Vec| { 102 | if let Some(include) = include_stack.last() { 103 | include.is_system_header 104 | } else { 105 | false 106 | } 107 | }; 108 | 109 | for line in stdout.lines() { 110 | if line.starts_with("# ") { 111 | let split: Vec<_> = line.split(' ').collect(); 112 | if split.len() >= 4 { 113 | // [#, lineno, "/path/to/source.cpp", 1, 3] 114 | let file = split[2].trim_matches('"'); 115 | let flags = &split[3..]; 116 | let is_system_header = flags.contains(&"3"); 117 | 118 | let was_including_system_header = is_including_system_header(&include_stack); 119 | 120 | if flags.contains(&"1") { 121 | // Start of a new file. 122 | include_stack.push(Include { 123 | file, 124 | is_system_header, 125 | }); 126 | 127 | if is_system_header && !was_including_system_header { 128 | for path in include_paths { 129 | if let Some(relative_include) = file.strip_prefix(path) { 130 | result 131 | .push_str(&format!("#include <{}>\n", &relative_include[1..])); 132 | break; 133 | } 134 | } 135 | } 136 | } else if flags.contains(&"2") { 137 | // End of an included file. 138 | let popped = include_stack.pop(); 139 | assert!(popped.is_some(), "cannot pop empty include stack"); 140 | 141 | if let Some(current_include) = include_stack.last() { 142 | assert_eq!(current_include.file, file); 143 | } 144 | 145 | // Skip the '# ... 2' line as the corresponding '# ... 1' was not emitted. 146 | if was_including_system_header { 147 | continue; 148 | } 149 | } 150 | } 151 | } 152 | 153 | // Skip lines that come from a system header, as those have been replaced with an #include. 154 | if is_including_system_header(&include_stack) { 155 | continue; 156 | } 157 | 158 | result.push_str(line); 159 | result.push('\n'); 160 | } 161 | 162 | result 163 | } 164 | 165 | fn run_preprocessor(entry: &json_compilation_db::Entry) -> Result { 166 | let mut command = entry.arguments.clone(); 167 | remove_c_and_output_flags(&mut command); 168 | 169 | let output = std::process::Command::new(&command[0]) 170 | .current_dir(&entry.directory) 171 | .args(&command[1..]) 172 | .arg("-E") 173 | .arg("-C") 174 | .arg("-v") 175 | .output()?; 176 | 177 | // Yes, we're assuming the source code uses UTF-8. 178 | // No, we don't care about other encodings like SJIS. 179 | let stdout = std::str::from_utf8(&output.stdout)?; 180 | let stderr = std::str::from_utf8(&output.stderr)?; 181 | 182 | // Post-process the preprocessed output to make it smaller by un-including 183 | // system headers (e.g. C and C++ standard library headers). 184 | let include_paths = get_include_paths(stderr); 185 | let result = uninclude_system_includes(stdout, &include_paths); 186 | 187 | Ok(result) 188 | } 189 | 190 | fn get_translation_unit( 191 | source_file: &str, 192 | compilation_db: &json_compilation_db::Entries, 193 | ) -> Result { 194 | let canonical_path = PathBuf::from(source_file).canonicalize()?; 195 | 196 | let entry = compilation_db 197 | .iter() 198 | .find(|entry| entry.file == canonical_path) 199 | .with_context(|| format!("failed to find source file {}", source_file))?; 200 | 201 | let command = entry.arguments.clone(); 202 | let contents = run_preprocessor(entry).context("failed to run preprocessor")?; 203 | 204 | Ok(TranslationUnit { 205 | command, 206 | contents, 207 | path: entry.file.to_string_lossy().to_string(), 208 | }) 209 | } 210 | 211 | /// Returns the URL of the scratch if successful. 212 | fn create_scratch( 213 | args: &Args, 214 | decomp_me_config: &repo::ConfigDecompMe, 215 | info: &functions::Info, 216 | flags: &str, 217 | context: &str, 218 | source_code: &str, 219 | disassembly: &str, 220 | ) -> Result { 221 | let client = reqwest::blocking::Client::new(); 222 | 223 | #[derive(serde::Serialize)] 224 | struct Data { 225 | compiler: String, 226 | compiler_flags: String, 227 | platform: String, 228 | name: String, 229 | diff_label: Option, 230 | target_asm: String, 231 | source_code: String, 232 | context: String, 233 | } 234 | 235 | let data = Data { 236 | compiler: decomp_me_config.compiler_name.clone(), 237 | compiler_flags: flags.to_string(), 238 | platform: "switch".to_string(), 239 | name: info.name.clone(), 240 | diff_label: Some(info.name.clone()), 241 | target_asm: disassembly.to_string(), 242 | source_code: source_code.to_string(), 243 | context: context.to_string(), 244 | }; 245 | 246 | let res_text = client 247 | .post(format!("{}/api/scratch", &args.decomp_me_api)) 248 | .json(&data) 249 | .send()? 250 | .text()?; 251 | 252 | #[derive(serde::Deserialize)] 253 | struct ResponseData { 254 | slug: String, 255 | } 256 | 257 | let res = serde_json::from_str::(&res_text); 258 | 259 | if let Some(error) = res.as_ref().err() { 260 | ui::print_error(&format!("failed to upload function: {}", error)); 261 | ui::print_note(&format!( 262 | "server response:\n{}\n", 263 | &res_text.normal().yellow() 264 | )); 265 | bail!("failed to upload function"); 266 | } 267 | 268 | let res_data = res.unwrap(); 269 | 270 | Ok(format!("{}/scratch/{}", args.decomp_me_api, res_data.slug)) 271 | } 272 | 273 | // Reimplement fmt::Display to use relative offsets rather than absolute addresses for labels. 274 | struct InstructionWrapper(bad64::Instruction); 275 | impl std::fmt::Display for InstructionWrapper { 276 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 277 | let insn = &self.0; 278 | 279 | write!(f, "{}", insn.op())?; 280 | 281 | for (index, op) in insn.operands().iter().enumerate() { 282 | if index != 0 { 283 | write!(f, ",")?; 284 | } 285 | 286 | match op { 287 | bad64::Operand::Label(bad64::Imm::Unsigned(x)) => { 288 | write!(f, " {}", x.wrapping_sub(insn.address()))? 289 | } 290 | _ => write!(f, " {}", op)?, 291 | } 292 | } 293 | 294 | Ok(()) 295 | } 296 | } 297 | 298 | fn get_disassembly(function_info: &functions::Info, function: &elf::Function) -> Result { 299 | let mut disassembly = String::new(); 300 | 301 | disassembly += &function_info.name; 302 | disassembly += ":\n"; 303 | 304 | let iter = bad64::disasm(function.code, function.addr); 305 | 306 | for maybe_insn in iter { 307 | if maybe_insn.is_err() { 308 | bail!("failed to disassemble: {:?}", maybe_insn) 309 | } 310 | 311 | let insn = InstructionWrapper(maybe_insn.unwrap()); 312 | disassembly += &insn.to_string(); 313 | disassembly.push('\n'); 314 | } 315 | 316 | Ok(disassembly) 317 | } 318 | 319 | /// Returns a path to the source file where the specified function is defined. 320 | fn deduce_source_file_from_debug_info( 321 | decomp_elf: &elf::OwnedElf, 322 | function_name: &str, 323 | ) -> Result { 324 | let symbol = elf::find_function_symbol_by_name(decomp_elf, function_name)?; 325 | 326 | let data: &[u8] = &decomp_elf.as_owner().1; 327 | let file = addr2line::object::read::File::parse(data)?; 328 | let ctx = addr2line::Context::new(&file)?; 329 | 330 | // Grab the location of the last frame (we choose the last frame to ignore inline function frames). 331 | let frame = ctx 332 | .find_frames(symbol.st_value)? 333 | .last()? 334 | .context("no frame found")?; 335 | 336 | let loc = frame.location.context("no location found")?; 337 | let file = loc.file.context("no file found")?; 338 | 339 | Ok(file.to_string()) 340 | } 341 | 342 | fn main() -> Result<()> { 343 | let args: Args = argh::from_env(); 344 | 345 | let config = repo::get_config(); 346 | let decomp_me_config = config 347 | .decomp_me 348 | .as_ref() 349 | .context("decomp.me integration needs to be configured")?; 350 | 351 | let functions = functions::get_functions(args.version.as_deref())?; 352 | 353 | let function_info = ui::fuzzy_search_function_interactively(&functions, &args.function_name)?; 354 | 355 | eprintln!("{}", ui::format_symbol_name(&function_info.name).bold()); 356 | 357 | let version = args.version.as_deref(); 358 | let decomp_elf = elf::load_decomp_elf(version)?; 359 | let orig_elf = elf::load_orig_elf(version)?; 360 | let function = elf::get_function(&orig_elf, function_info.addr, function_info.size as u64)?; 361 | let disassembly = get_disassembly(function_info, &function)?; 362 | 363 | let source_code = format!( 364 | "// function name: {}\n\ 365 | // original address: {:#x} \n\ 366 | \n\ 367 | // move the target function from the context to the source tab", 368 | &function_info.name, 369 | function_info.get_start(), 370 | ); 371 | 372 | let mut flags = decomp_me_config.default_compile_flags.clone(); 373 | let mut context = "".to_string(); 374 | 375 | // Fill in compile flags and the context using the compilation database 376 | // and the specified source file. 377 | let source_file = args 378 | .source_file 379 | .clone() 380 | .or_else(|| deduce_source_file_from_debug_info(&decomp_elf, &function_info.name).ok()); 381 | 382 | if let Some(source_file) = source_file.as_deref() { 383 | println!("source file: {}", &source_file.dimmed()); 384 | 385 | let compilation_db = 386 | load_compilation_database(&args).context("failed to load compilation database")?; 387 | 388 | let tu = get_translation_unit(source_file, &compilation_db) 389 | .context("failed to get translation unit")?; 390 | 391 | context = tu.contents.clone(); 392 | 393 | let mut command = tu.command.clone(); 394 | remove_c_and_output_flags(&mut command); 395 | // Remove the compiler command name. 396 | command.remove(0); 397 | // Remove the sysroot parameter, include dirs and source file path. 398 | command.retain(|arg| { 399 | if arg.starts_with("--sysroot=") { 400 | return false; 401 | } 402 | if arg.starts_with("-I") { 403 | return false; 404 | } 405 | if arg == &tu.path { 406 | return false; 407 | } 408 | true 409 | }); 410 | 411 | flags = command.join(" "); 412 | flags += " -x c++"; 413 | } else { 414 | ui::print_warning( 415 | "consider passing -s [.cpp source] so that the context can be automatically filled", 416 | ); 417 | } 418 | 419 | println!("context: {} lines", context.matches('\n').count()); 420 | println!("compile flags: {}", &flags.dimmed()); 421 | 422 | let confirm = inquire::Confirm::new("Upload?") 423 | .with_default(true) 424 | .with_help_message("note that your source file paths will be revealed if you continue"); 425 | if !confirm.prompt()? { 426 | bail!("cancelled"); 427 | } 428 | 429 | println!("uploading..."); 430 | 431 | let url = create_scratch( 432 | &args, 433 | decomp_me_config, 434 | function_info, 435 | &flags, 436 | &context, 437 | &source_code, 438 | &disassembly, 439 | ) 440 | .context("failed to create scratch")?; 441 | 442 | ui::print_note(&format!("created scratch: {}", &url)); 443 | 444 | Ok(()) 445 | } 446 | -------------------------------------------------------------------------------- /viking/src/tools/list_symbols.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use argh::FromArgs; 3 | use colored::Colorize; 4 | use goblin::{ 5 | elf::Sym, 6 | elf64::sym::{STT_FILE, STT_NOTYPE, STT_OBJECT}, 7 | }; 8 | use itertools::Itertools; 9 | use viking::{elf, functions}; 10 | 11 | #[derive(FromArgs)] 12 | /// Print symbols in the output ELF. 13 | struct Args { 14 | /// show symbols that are undefined (e.g. unimplemented functions) 15 | #[argh(switch, short = 'u')] 16 | show_undefined: bool, 17 | 18 | /// show data symbols 19 | #[argh(switch, short = 'd')] 20 | show_data: bool, 21 | 22 | /// show symbols that are listed as decompiled in the function CSV 23 | #[argh(switch, short = 'l')] 24 | show_decompiled: bool, 25 | 26 | /// show only symbols containing the specified substring 27 | #[argh(positional)] 28 | filter: Option, 29 | 30 | /// version 31 | #[argh(option)] 32 | version: Option, 33 | } 34 | 35 | fn main() -> Result<()> { 36 | colored::control::set_override(true); 37 | 38 | let args: Args = argh::from_env(); 39 | 40 | let functions = functions::get_functions(args.version.as_deref())?; 41 | let known_funcs = functions::make_known_function_name_map(&functions); 42 | 43 | let elf = elf::load_decomp_elf(args.version.as_deref())?; 44 | let symtab = elf::SymbolStringTable::from_elf(&elf)?; 45 | 46 | let filter = |sym: &Sym| { 47 | if sym.st_type() == STT_NOTYPE && sym.st_value != 0 { 48 | return false; 49 | } 50 | 51 | if sym.st_type() == STT_FILE { 52 | return false; 53 | } 54 | 55 | if !args.show_undefined && elf::is_undefined_sym(sym) { 56 | return false; 57 | } 58 | 59 | if !args.show_data && sym.st_type() == STT_OBJECT { 60 | return false; 61 | } 62 | 63 | true 64 | }; 65 | 66 | let mut prev_addr = 0u64; 67 | 68 | for symbol in elf 69 | .syms 70 | .iter() 71 | .filter(filter) 72 | .sorted_by_key(|sym| sym.st_value) 73 | { 74 | // Skip duplicate symbols (e.g. C1 / C2 for ctors in classes that do not use virtual inheritance). 75 | // Because the C1 symbol is usually emitted first, this means that C1 will be printed but not C2. 76 | if symbol.st_value != 0 && symbol.st_value == prev_addr { 77 | continue; 78 | } 79 | prev_addr = symbol.st_value; 80 | 81 | let name = symtab.get_string(symbol.st_name); 82 | let func_entry = known_funcs.get(name); 83 | 84 | if let Some(func_entry) = func_entry { 85 | if !args.show_decompiled && func_entry.is_decompiled() { 86 | continue; 87 | } 88 | } 89 | 90 | let demangled_name = functions::demangle_str(name).unwrap_or_else(|_| name.to_string()); 91 | 92 | if let Some(filter) = &args.filter { 93 | if !demangled_name.contains(filter) && !name.contains(filter) { 94 | continue; 95 | } 96 | } 97 | 98 | let status = if elf::is_undefined_sym(&symbol) { 99 | "undef ".red().bold() 100 | } else if symbol.st_type() == STT_OBJECT { 101 | "data ".normal().bold() 102 | } else if let Some(func_entry) = func_entry { 103 | if func_entry.is_decompiled() { 104 | "listed ".green().bold() 105 | } else { 106 | "listed ".cyan().bold() 107 | } 108 | } else { 109 | "unknown".yellow().bold() 110 | }; 111 | 112 | println!("{} {} ({})", status, &demangled_name, name.dimmed()); 113 | } 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /viking/src/ui.rs: -------------------------------------------------------------------------------- 1 | use anyhow::bail; 2 | use anyhow::Result; 3 | use colored::*; 4 | use itertools::Itertools; 5 | use std::io::StderrLock; 6 | use std::io::Write; 7 | use textwrap::indent; 8 | 9 | use crate::functions; 10 | 11 | pub fn print_note(msg: &str) { 12 | eprintln!("{}{}{}", "note".bold().cyan(), ": ".bold(), msg.bold()) 13 | } 14 | 15 | pub fn print_warning(msg: &str) { 16 | eprintln!("{}{}{}", "warning".bold().yellow(), ": ".bold(), msg.bold()) 17 | } 18 | 19 | pub fn print_error(msg: &str) { 20 | let stderr = std::io::stderr(); 21 | let mut lock = stderr.lock(); 22 | print_error_ex(&mut lock, msg); 23 | } 24 | 25 | pub fn print_error_ex(lock: &mut StderrLock, msg: &str) { 26 | writeln!( 27 | lock, 28 | "{}{}{}", 29 | "error".bold().red(), 30 | ": ".bold(), 31 | msg.bold() 32 | ) 33 | .unwrap(); 34 | } 35 | 36 | pub fn format_symbol_name(name: &str) -> String { 37 | functions::demangle_str(name).map_or(name.blue().to_string(), |demangled| { 38 | format!("{} ({})", demangled.blue(), name.blue().dimmed(),) 39 | }) 40 | } 41 | 42 | pub fn format_address(addr: u64) -> String { 43 | format!("{:#x}", addr).green().to_string() 44 | } 45 | 46 | pub fn print_detail(msg: &str) { 47 | let stderr = std::io::stderr(); 48 | let mut lock = stderr.lock(); 49 | print_detail_ex(&mut lock, msg); 50 | } 51 | 52 | pub fn print_detail_ex(lock: &mut StderrLock, msg: &str) { 53 | writeln!(lock, "{}\n", indent(&msg.clear(), &" │ ".bold().dimmed())).unwrap(); 54 | } 55 | 56 | pub fn init_prompt_settings() { 57 | let mut config = inquire::ui::RenderConfig::default(); 58 | config.prompt.att |= inquire::ui::Attributes::BOLD; 59 | inquire::set_global_render_config(config); 60 | } 61 | 62 | pub fn clear_terminal() { 63 | crossterm::execute!( 64 | std::io::stdout(), 65 | crossterm::terminal::Clear(crossterm::terminal::ClearType::All), 66 | crossterm::cursor::MoveTo(0, 0), 67 | ) 68 | .unwrap_or(()); 69 | } 70 | 71 | pub fn fuzzy_search_function_interactively<'a>( 72 | functions: &'a [functions::Info], 73 | name: &str, 74 | ) -> Result<&'a functions::Info> { 75 | let candidates = functions::fuzzy_search(functions, name); 76 | match candidates[..] { 77 | [] => bail!("no match for {}", format_symbol_name(name)), 78 | [exact_match] => Ok(exact_match), 79 | _ => { 80 | let prompt = format!( 81 | "{} is ambiguous (found {} matches); did you mean:", 82 | name.dimmed(), 83 | candidates.len() 84 | ); 85 | 86 | clear_terminal(); 87 | 88 | let options = candidates 89 | .iter() 90 | .map(|info| format_symbol_name(&info.name)) 91 | .collect_vec(); 92 | 93 | let selection = inquire::Select::new(&prompt, options) 94 | .with_starting_cursor(0) 95 | .with_page_size(crossterm::terminal::size()?.1 as usize / 2) 96 | .raw_prompt()? 97 | .index; 98 | 99 | Ok(candidates[selection]) 100 | } 101 | } 102 | } 103 | --------------------------------------------------------------------------------