From f43697646592d2e0b97dbab0c75d09686351e68e Mon Sep 17 00:00:00 2001 From: abaresk <46002898+abaresk@users.noreply.github.com> Date: Fri, 17 Jun 2022 05:42:23 -0400 Subject: [PATCH] Add arch settings for ARM32 (#127) --- README.md | 4 +- arm32_compile_example.sh | 19 ++ ..._example.sh => mwcceppc_compile_example.sh | 0 src/objdump.py | 178 ++++++++++++------ src/scorer.py | 4 + 5 files changed, 148 insertions(+), 57 deletions(-) create mode 100644 arm32_compile_example.sh rename mwcc_compile_example.sh => mwcceppc_compile_example.sh (100%) diff --git a/README.md b/README.md index 4c2cc67..2c68943 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,7 @@ The modes can also be combined, by using the `PERM_RANDOMIZE` macro. [](https://asciinema.org/a/232846) -The main target for the tool is MIPS code compiled by old compilers (IDO, possibly GCC). -Getting it to work on other architectures shouldn't be too hard, however. -https://github.com/laqieer/decomp-permuter-arm has an ARM port. +This tool supports MIPS (compiled by IDO, possibly GCC), PowerPC, and ARM32 assembly. ## Usage diff --git a/arm32_compile_example.sh b/arm32_compile_example.sh new file mode 100644 index 0000000..d21d49e --- /dev/null +++ b/arm32_compile_example.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +### NOTE: Ensure that `arm-none-eabi-objdump` is in your environment PATH var. + +### Set this to the root of your decomp project repository. +WORKING_DIR="/home/user/pokeheartgold" + +### Set this to the relative path of the C compiler in your repo. +CC="tools/mwccarm/2.0/sp2p2/mwccarm.exe" + +C_NAME="$1" +O_NAME="$3" + +pushd $WORKING_DIR + +### Change any compile flags here, and remove wine if you are using windows ofc. +wine $CC -O4,p -enum int -lang c99 -Cpp_exceptions off -gccext,on -proc arm946e -msgstyle gcc -gccinc -i ./include -ipa file -interworking -inline on,noauto -char signed -gccdep -MD -c -o $O_NAME $C_NAME + +popd diff --git a/mwcc_compile_example.sh b/mwcceppc_compile_example.sh similarity index 100% rename from mwcc_compile_example.sh rename to mwcceppc_compile_example.sh diff --git a/src/objdump.py b/src/objdump.py index d176e40..a9ef0d7 100644 --- a/src/objdump.py +++ b/src/objdump.py @@ -40,6 +40,7 @@ class ArchSettings: re_reg: Pattern[str] re_sprel: Pattern[str] re_includes_sp: Pattern[str] + sp_ref_insns: List[str] reloc_str: str branch_instructions: Set[str] forbidden: Set[str] = field(default_factory=lambda: set(string.ascii_letters + "_")) @@ -100,6 +101,34 @@ PPC_BRANCH_INSTRUCTIONS = { PPC_BRANCH_LIKELY_INSTRUCTIONS: Set[str] = set() +ARM32_PREFIXES = {"b", "bl"} +ARM32_CONDS = { + "", + "eq", + "ne", + "cs", + "cc", + "mi", + "pl", + "vs", + "vc", + "hi", + "ls", + "ge", + "lt", + "gt", + "le", + "al", +} +ARM32_SUFFIXES = {"", ".n", ".w"} +ARM32_BRANCH_INSTRUCTIONS = { + f"{prefix}{cond}{suffix}" + for prefix in ARM32_PREFIXES + for cond in ARM32_CONDS + for suffix in ARM32_SUFFIXES +} + + MIPS_SETTINGS: ArchSettings = ArchSettings( name="mips", re_comment=re.compile(r"<.*?>"), @@ -108,6 +137,7 @@ MIPS_SETTINGS: ArchSettings = ArchSettings( ), re_sprel=re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\((sp|s8)\)"), re_includes_sp=re.compile(r"\b(sp|s8)\b"), + sp_ref_insns=["addiu"], reloc_str="R_MIPS_", objdump=["mips-linux-gnu-objdump", "-drz", "-m", "mips:4300"], branch_likely_instructions=MIPS_BRANCH_LIKELY_INSTRUCTIONS, @@ -118,6 +148,7 @@ MIPS_SETTINGS: ArchSettings = ArchSettings( PPC_SETTINGS: ArchSettings = ArchSettings( name="ppc", re_includes_sp=re.compile(r"\b(r1)\b"), + sp_ref_insns=[], re_comment=re.compile(r"(<.*>|//.*$)"), re_reg=re.compile(r"\$?\b([rf][0-9]+)\b"), re_sprel=re.compile(r"(?<=,)(-?[0-9]+|-?0x[0-9a-f]+)\(r1\)"), @@ -128,6 +159,27 @@ PPC_SETTINGS: ArchSettings = ArchSettings( ) +ARM32_SETTINGS: ArchSettings = ArchSettings( + name="arm32", + re_includes_sp=re.compile(r"\b(sp)\b"), + sp_ref_insns=["add", "sub"], + re_comment=re.compile(r"(<.*>|//.*$)"), + # Includes: + # - General purpose registers: r0..13 + # - Frame pointer registers: lr (r14), pc (r15) + # - VFP/NEON registers: s0..31, d0..31, q0..15, fpscr, fpexc, fpsid + # SP should not be in this list. + re_reg=re.compile( + r"\$?\b([rq][0-9]|[rq]1[0-5]|pc|lr|[ds][12]?[0-9]|[ds]3[01]|fp(scr|exc|sid))\b" + ), + re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), + reloc_str="R_ARM_", + objdump=["arm-none-eabi-objdump", "-drz"], + branch_instructions=ARM32_BRANCH_INSTRUCTIONS, + branch_likely_instructions=set(), +) + + def get_arch(o_file: str) -> ArchSettings: # https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html with open(o_file, "rb") as f: @@ -140,7 +192,8 @@ def get_arch(o_file: str) -> ArchSettings: return MIPS_SETTINGS if arch == 20: return PPC_SETTINGS - # TODO: support ARM (0x28) + if arch == 40: + return ARM32_SETTINGS raise Exception("Bad ELF") @@ -189,6 +242,67 @@ def pre_process(mnemonic: str, args: str, next_row: Optional[str]) -> Tuple[str, return mnemonic, args +def process_mips_reloc(reloc_row: str, prev: str, repl: str, imm: str) -> str: + # Sometimes s8 is used as a non-framepointer, but we've already lost + # the immediate value by pretending it is one. This isn't too bad, + # since it's rare and applies consistently. But we do need to handle it + # here to avoid a crash, by pretending that lost imms are zero for + # relocations. + if imm != "0" and imm != "imm" and imm != "addr": + repl += "+" + imm if int(imm, 0) > 0 else imm + if any( + reloc in reloc_row + for reloc in ["R_MIPS_LO16", "R_MIPS_LITERAL", "R_MIPS_GPREL16"] + ): + repl = f"%lo({repl})" + elif "R_MIPS_HI16" in reloc_row: + # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a + # correct addend for each, but objdump doesn't give us the order of + # the relocations, so we can't find the right LO16. :( + repl = f"%hi({repl})" + else: + assert "R_MIPS_26" in reloc_row, f"unknown relocation type '{reloc_row}'" + return repl + + +def process_ppc_reloc(reloc_row: str, prev: str, repl: str) -> str: + assert any( + r in reloc_row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] + ), f"unknown relocation type '{reloc_row}' for line '{prev}'" + + if "R_PPC_REL24" in reloc_row: + # function calls + return repl + elif "R_PPC_ADDR16_HI" in reloc_row: + # absolute hi of addr + return f"{repl}@h" + elif "R_PPC_ADDR16_HA" in reloc_row: + # adjusted hi of addr + return f"{repl}@ha" + elif "R_PPC_ADDR16_LO" in reloc_row: + # lo of addr + return f"{repl}@l" + elif "R_PPC_ADDR16" in reloc_row: + # 16-bit absolute addr + if "+0x7" in repl: + # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) + # computations and are unimportant in a diff setting. + if int(repl.split("+")[1], 16) > 0x70000000: + return repl.split("+")[0] + elif "R_PPC_EMB_SDA21" in reloc_row: + # sda21 relocations; r2/r13 --> 0 swaps are performed in an earlier processing step + return f"{repl}@sda21" + return repl + + +def process_arm32_reloc(reloc_row: str, prev: str, repl: str) -> str: + assert any( + r in reloc_row for r in ["R_ARM_THM_CALL", "R_ARM_CALL", "R_ARM_ABS32"] + ), f"unknown relocation type '{reloc_row}' for line '{prev}'" + + return repl + + def process_reloc(reloc_row: str, prev: str) -> Optional[str]: if prev == "": return None @@ -201,60 +315,16 @@ def process_reloc(reloc_row: str, prev: str) -> Optional[str]: assert ign_branch_targets return None - if "R_PPC_" in reloc_row: - - assert any( - r in reloc_row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] - ), f"unknown relocation type '{reloc_row}' for line '{prev}'" - - if "R_PPC_REL24" in reloc_row: - # function calls - pass - elif "R_PPC_ADDR16_HI" in reloc_row: - # absolute hi of addr - repl = f"{repl}@h" - elif "R_PPC_ADDR16_HA" in reloc_row: - # adjusted hi of addr - repl = f"{repl}@ha" - elif "R_PPC_ADDR16_LO" in reloc_row: - # lo of addr - repl = f"{repl}@l" - elif "R_PPC_ADDR16" in reloc_row: - # 16-bit absolute addr - if "+0x7" in repl: - # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) - # computations and are unimportant in a diff setting. - if int(repl.split("+")[1], 16) > 0x70000000: - repl = repl.split("+")[0] - elif "R_PPC_EMB_SDA21" in reloc_row: - # sda21 relocations; r2/r13 --> 0 swaps are performed in an earlier processing step - repl = f"{repl}@sda21" - - return before + repl + after - if "R_MIPS_" in reloc_row: - # Sometimes s8 is used as a non-framepointer, but we've already lost - # the immediate value by pretending it is one. This isn't too bad, - # since it's rare and applies consistently. But we do need to handle it - # here to avoid a crash, by pretending that lost imms are zero for - # relocations. - if imm != "0" and imm != "imm" and imm != "addr": - repl += "+" + imm if int(imm, 0) > 0 else imm - if any( - reloc in reloc_row - for reloc in ["R_MIPS_LO16", "R_MIPS_LITERAL", "R_MIPS_GPREL16"] - ): - repl = f"%lo({repl})" - elif "R_MIPS_HI16" in reloc_row: - # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a - # correct addend for each, but objdump doesn't give us the order of - # the relocations, so we can't find the right LO16. :( - repl = f"%hi({repl})" - else: - assert "R_MIPS_26" in reloc_row, f"unknown relocation type '{reloc_row}'" - return before + repl + after + new_repl = process_mips_reloc(reloc_row, prev, repl, imm) + elif "R_PPC_" in reloc_row: + new_repl = process_ppc_reloc(reloc_row, prev, repl) + elif "R_ARM_" in reloc_row: + new_repl = process_arm32_reloc(reloc_row, prev, repl) + else: + raise Exception(f"unknown relocation type: {reloc_row}") - raise Exception(f"unknown relocation type: {reloc_row}") + return before + new_repl + after def simplify_objdump( @@ -305,7 +375,7 @@ def simplify_objdump( row = re.sub(arch.re_reg, "", row) if not stack_differences: - if mnemonic == "addiu" and arch.re_includes_sp.search(args): + if mnemonic in arch.sp_ref_insns and arch.re_includes_sp.search(args): row = re.sub(re_int_full, "imm", row) if mnemonic in arch.branch_instructions: if ign_branch_targets: diff --git a/src/scorer.py b/src/scorer.py index 2abf553..fbf93bd 100644 --- a/src/scorer.py +++ b/src/scorer.py @@ -59,6 +59,10 @@ class Scorer: if arch.name == "mips": return "." in field + # Example: ".text+0x34" + if arch.name == "arm32": + return "." in field + return False def diff_sameline(old_line: Line, new_line: Line) -> None: