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: