mirror of
https://github.com/doldecomp/melee.git
synced 2024-11-23 05:19:59 +00:00
Create script for finding easy functions (#1462)
usage: easy_funcs.py [-h] [-s BYTES] [-S BYTES] [-v HEX] [-V HEX] [-m PERCENT] [-M PERCENT] [-n RESULTS] [UNITS ...] positional arguments: UNITS directories or files to search options: -h, --help show this help message and exit -s BYTES, --min-size BYTES the minimum size for a function -S BYTES, --max-size BYTES the maximum size for a function (default 512) -v HEX, --min-virtual-address HEX the minimum virtual address for a function -V HEX, --max-virtual-address HEX the maximum virtual address for a function -m PERCENT, --min-matched PERCENT the minimum fuzzy match % for a function -M PERCENT, --max-matched PERCENT the maximum fuzzy match % for a function (default 0%) -n RESULTS, --max-results RESULTS the maximum number of functions to display
This commit is contained in:
parent
8b105fcc96
commit
8aea0d0e8b
@ -1,2 +1,4 @@
|
||||
mistletoe
|
||||
GitPython
|
||||
humanfriendly
|
||||
mistletoe
|
||||
prettytable
|
||||
|
@ -2,13 +2,19 @@
|
||||
# This file is autogenerated by pip-compile with Python 3.12
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile reqs/misc.in
|
||||
# pip-compile --output-file=reqs/misc.txt reqs/misc.in
|
||||
#
|
||||
gitdb==4.0.11
|
||||
# via gitpython
|
||||
gitpython==3.1.42
|
||||
# via -r reqs/misc.in
|
||||
humanfriendly==10.0
|
||||
# via -r reqs/misc.in
|
||||
mistletoe==1.4.0
|
||||
# via -r reqs/misc.in
|
||||
prettytable==3.11.0
|
||||
# via -r reqs/misc.in
|
||||
smmap==5.0.1
|
||||
# via gitdb
|
||||
wcwidth==0.2.13
|
||||
# via prettytable
|
||||
|
237
tools/easy_funcs.py
Executable file
237
tools/easy_funcs.py
Executable file
@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import cast
|
||||
|
||||
import humanfriendly
|
||||
import prettytable
|
||||
|
||||
type ReportDict = dict[str, ReportValue]
|
||||
type ReportList = list[ReportValue]
|
||||
type ReportValue = ReportDict | ReportList | str
|
||||
|
||||
ROOT = Path(__file__).parents[1]
|
||||
MODULE = "main"
|
||||
REPORT_PATH = "build/GALE01/report.json"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Function:
|
||||
name: str
|
||||
unit: str
|
||||
size: int
|
||||
matched: float
|
||||
address: int
|
||||
|
||||
|
||||
def create_trie(units: list[str]) -> re.Pattern[str]:
|
||||
return re.compile(rf"^{MODULE}/(?:{'|'.join(units)})(?=/|$)" if units else ".*")
|
||||
|
||||
|
||||
def get_report() -> ReportDict:
|
||||
|
||||
def run_ninja():
|
||||
proc = subprocess.run(
|
||||
["ninja", str(REPORT_PATH)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
print(proc.stdout.decode(), file=sys.stderr)
|
||||
print(proc.stderr.decode(), file=sys.stderr)
|
||||
|
||||
run_ninja()
|
||||
|
||||
with ROOT.joinpath(REPORT_PATH).open("r") as fp:
|
||||
report = cast(ReportDict, json.load(fp))
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def print_funcs(
|
||||
units: list[str],
|
||||
size: tuple[int, int],
|
||||
matched: tuple[float, float],
|
||||
address: tuple[int, int],
|
||||
max_results: int,
|
||||
):
|
||||
trie = create_trie(units)
|
||||
report = get_report()
|
||||
|
||||
results: list[Function] = []
|
||||
|
||||
for unit in map(
|
||||
lambda u: cast(ReportDict, u),
|
||||
cast(ReportList, report["units"]),
|
||||
):
|
||||
|
||||
unit_name = cast(str, unit["name"])
|
||||
m = trie.match(unit_name)
|
||||
if m is None:
|
||||
continue
|
||||
|
||||
unit_name = unit_name.removeprefix(f"{MODULE}/")
|
||||
|
||||
functions = cast(list[ReportDict], unit.get("functions", []))
|
||||
for function in functions:
|
||||
if max_results > 0 and len(results) >= max_results:
|
||||
break
|
||||
|
||||
func_name = cast(str, function["name"])
|
||||
|
||||
func_size = int(cast(str, function["size"]))
|
||||
if func_size < size[0] or func_size > size[1]:
|
||||
continue
|
||||
|
||||
func_matched = float(cast(str, function.get("fuzzy_match_percent", 0)))
|
||||
if func_matched < matched[0] or func_matched > matched[1]:
|
||||
continue
|
||||
|
||||
func_addr = int(
|
||||
cast(dict[str, str], function.get("metadata", {})).get(
|
||||
"virtual_address", "0"
|
||||
)
|
||||
)
|
||||
if func_addr < address[0] or func_addr > address[1]:
|
||||
continue
|
||||
|
||||
results.append(
|
||||
Function(
|
||||
func_name,
|
||||
unit_name,
|
||||
func_size,
|
||||
func_matched,
|
||||
func_addr,
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
continue
|
||||
break
|
||||
|
||||
results.sort(key=lambda f: (f.size, f.address))
|
||||
table = prettytable.PrettyTable()
|
||||
table.field_names = ["Address", "Unit", "Function", "Size", "Matched"]
|
||||
table.align["Address"] = "c"
|
||||
table.align["Unit"] = "l"
|
||||
table.align["Function"] = "l"
|
||||
table.align["Size"] = "r"
|
||||
table.align["Matched"] = "r"
|
||||
table.set_style(prettytable.PLAIN_COLUMNS)
|
||||
table.add_rows(
|
||||
[
|
||||
[
|
||||
f"{f.address:08X}",
|
||||
f.unit,
|
||||
f.name,
|
||||
humanfriendly.format_size(f.size),
|
||||
f"{f.matched:.2f}%",
|
||||
]
|
||||
for f in results
|
||||
],
|
||||
)
|
||||
print(table)
|
||||
|
||||
|
||||
def main():
|
||||
def sanitize_path(s: str) -> str:
|
||||
p = PurePosixPath(s.replace("\\", "/"))
|
||||
s = str(p.parent / p.stem)
|
||||
for parent in [
|
||||
ROOT.as_posix(),
|
||||
"build/GALE01",
|
||||
"src",
|
||||
"asm",
|
||||
"/",
|
||||
]:
|
||||
s = s.removeprefix(parent)
|
||||
return s
|
||||
|
||||
def sanitize_hex(s: str) -> int:
|
||||
return int(s.removeprefix("0x"), 16)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
_ = parser.add_argument(
|
||||
"units",
|
||||
nargs="*",
|
||||
type=sanitize_path,
|
||||
metavar="UNITS",
|
||||
help="directories or files to search",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-s",
|
||||
"--min-size",
|
||||
type=int,
|
||||
default=0,
|
||||
metavar="BYTES",
|
||||
help="the minimum size for a function",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-S",
|
||||
"--max-size",
|
||||
type=int,
|
||||
default=512,
|
||||
metavar="BYTES",
|
||||
help="the maximum size for a function (default 512)",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-v",
|
||||
"--min-virtual-address",
|
||||
dest="min_address",
|
||||
type=sanitize_hex,
|
||||
default=0,
|
||||
metavar="HEX",
|
||||
help="the minimum virtual address for a function",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-V",
|
||||
"--max-virtual-address",
|
||||
dest="max_address",
|
||||
type=sanitize_hex,
|
||||
default=0xFFFFFFFF,
|
||||
metavar="HEX",
|
||||
help="the maximum virtual address for a function",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-m",
|
||||
"--min-matched",
|
||||
type=float,
|
||||
default=0,
|
||||
metavar="PERCENT",
|
||||
help="the minimum fuzzy match %% for a function",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-M",
|
||||
"--max-matched",
|
||||
type=float,
|
||||
default=0,
|
||||
metavar="PERCENT",
|
||||
help="the maximum fuzzy match %% for a function (default 0%%)",
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"-n",
|
||||
"--max-results",
|
||||
type=lambda n: max(0, int(n)),
|
||||
default=0,
|
||||
metavar="RESULTS",
|
||||
help="the maximum number of functions to display",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print_funcs(
|
||||
cast(list[str], args.units),
|
||||
(cast(int, args.min_size), cast(int, args.max_size)),
|
||||
(cast(float, args.min_matched), cast(float, args.max_matched)),
|
||||
(cast(int, args.min_address), cast(int, args.max_address)),
|
||||
cast(int, args.max_results),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user