inital commit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import pydantic
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Protocol, cast
|
||||
|
||||
|
||||
CONFIG_ROOT = Path(__file__).parent.parent.parent.resolve() / "config"
|
||||
|
||||
# enumerate languages from config dir
|
||||
LANGUAGES = [Path(f.path).name for f in os.scandir(CONFIG_ROOT) if f.is_dir()]
|
||||
|
||||
RE_INSTR = re.compile(r"^\t(?:[^.]|\.p2align \d+)")
|
||||
|
||||
|
||||
class InstructionPatch(pydantic.RootModel[tuple[int, str, str]]):
|
||||
root: tuple[int, str, str]
|
||||
|
||||
@property
|
||||
def instr_no(self):
|
||||
return self.root[0]
|
||||
|
||||
@property
|
||||
def instr_org(self):
|
||||
return self.root[1]
|
||||
|
||||
@property
|
||||
def instr_patch(self):
|
||||
return self.root[2]
|
||||
|
||||
|
||||
class ASMPatch(pydantic.RootModel[dict[str, list[InstructionPatch]]]):
|
||||
root: dict[str, list[InstructionPatch]]
|
||||
|
||||
|
||||
class PatchDB(pydantic.RootModel[dict[str, ASMPatch]]):
|
||||
root: dict[str, ASMPatch]
|
||||
|
||||
|
||||
def fix_asm(asm_file: Path, asm_patch: ASMPatch):
|
||||
lines = asm_file.read_text().splitlines()
|
||||
|
||||
def find_func(func: str):
|
||||
for i, line in enumerate(lines):
|
||||
if re.match(rf"^{func}:", line):
|
||||
return i
|
||||
|
||||
return -1
|
||||
|
||||
def find_line(start: int, num: int):
|
||||
offset = 0
|
||||
instr_no = -1
|
||||
for line in lines[start:]:
|
||||
if RE_INSTR.match(line):
|
||||
instr_no += 1
|
||||
|
||||
if instr_no == num:
|
||||
return start + offset
|
||||
|
||||
offset += 1
|
||||
|
||||
raise RuntimeError("cannot find instruction!")
|
||||
|
||||
for func, patch_lst in asm_patch.root.items():
|
||||
n = find_func(func)
|
||||
|
||||
if n == -1:
|
||||
print(f"WARNING: cannot find function {func} in {asm_file.name}")
|
||||
continue
|
||||
|
||||
for patch in patch_lst:
|
||||
line_no = find_line(n, patch.instr_no)
|
||||
line_org = lines[line_no]
|
||||
|
||||
line_org_clean = re.sub(r"\s+", " ", line_org).strip()
|
||||
instr_org_clean = re.sub(r"\s+", " ", patch.instr_org).strip()
|
||||
instr_patch_clean = re.sub(r"\s+", " ", patch.instr_patch).strip()
|
||||
|
||||
if line_org_clean != instr_org_clean:
|
||||
print(f"WARNING: wrong line: {asm_file.name}:{func}:{line_no + 1}: {line_org} != {patch.instr_org}")
|
||||
continue
|
||||
|
||||
lines[line_no] = f"\t{instr_patch_clean}"
|
||||
|
||||
asm_file.write_text("\n".join(lines))
|
||||
|
||||
|
||||
def main():
|
||||
class ArgsProtocol(Protocol):
|
||||
language: str
|
||||
asm_file: Path
|
||||
|
||||
parser = argparse.ArgumentParser(description="apply asm patches to assembly files")
|
||||
parser.add_argument("language", type=str, choices=LANGUAGES, help="language of the asm that is being patched")
|
||||
parser.add_argument("asm_file", type=Path, help="generated assembly file to patch (relative to build dir)")
|
||||
|
||||
args = cast(ArgsProtocol, parser.parse_args())
|
||||
|
||||
asm_file = CONFIG_ROOT / args.language / args.asm_file
|
||||
|
||||
if not asm_file.exists():
|
||||
print(f"ERROR: cannot find assembly file {asm_file}")
|
||||
exit(1)
|
||||
|
||||
patch_db_path = CONFIG_ROOT / args.language / "asm_patches.json"
|
||||
|
||||
if not patch_db_path.exists():
|
||||
print("ERROR: patch db file doesn't exist. aborting patching asm")
|
||||
exit(1)
|
||||
|
||||
patch_db = PatchDB.model_validate_json(patch_db_path.read_text())
|
||||
|
||||
if asm_file.name not in patch_db.root:
|
||||
print(f"ERROR: no patches found for file {asm_file.name}")
|
||||
exit(1)
|
||||
|
||||
asm_patch = patch_db.root[asm_file.name]
|
||||
|
||||
fix_asm(asm_file, asm_patch)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user