summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--tools/pragma/README.md19
-rw-r--r--tools/pragma/pragma.py96
3 files changed, 83 insertions, 36 deletions
diff --git a/Makefile b/Makefile
index b897334..876487d 100644
--- a/Makefile
+++ b/Makefile
@@ -155,7 +155,7 @@ $(BUILD_DIR)/%.o: %.s
$(PYTHON) $(POSTPROC) $(PROCFLAGS) $@
$(BUILD_DIR)/%.o: %.cpp
- $(PYTHON) $(PRAGMAPROC) "$(CC)" "$(CFLAGS) -lang c++ -c" $@ $< -fix-regswaps
+ $(PYTHON) $(PRAGMAPROC) "$(CC)" "$(CFLAGS) -lang c++ -c" $@ $<
$(BUILD_DIR)/%.o: %.c
- $(PYTHON) $(PRAGMAPROC) "$(CC)" "$(CFLAGS) -lang c99 -c" $@ $< -fix-regswaps
+ $(PYTHON) $(PRAGMAPROC) "$(CC)" "$(CFLAGS) -lang c99 -c" $@ $<
diff --git a/tools/pragma/README.md b/tools/pragma/README.md
index 2f9074c..3823b42 100644
--- a/tools/pragma/README.md
+++ b/tools/pragma/README.md
@@ -2,12 +2,12 @@
#### Pragma processor for GameCube/Wii decompilation projects
###### by Max Parisi
-This is a custom pragma processor meant for use with matching decompilation projects for GameCube and Wii games. It currently adds support for `#pragma regswap`.
+This is a custom pragma processor meant for use with matching decompilation projects for GameCube and Wii games. It currently adds support for `#pragma regswap` and `#pragma iswap`.
-If some reconstructed C/C++ source code compiles to object binary whose only differences with a target binary are misallocated registers, you can use `#pragma regswap`
-to correct the register allocation. This is an alternative to the strategy of inlining the desired assembly that aims to be more compact, self-documenting, and convenient.
+If some reconstructed C/C++ source code compiles to object binary whose only differences with a target binary are misassigned registers, you can use `#pragma regswap`
+to correct the register assignment. This is an alternative to the strategy of inlining the desired assembly that aims to be more compact, self-documenting, and convenient.
-`pragma.py` will compile your source code, then fix the regswaps by patching the range of the object file specified in each pragma invocation.
+`pragma.py` will compile your source code, then perform the register and instruction swaps by patching the range of the object file specified in each pragma invocation.
## Usage
Invoke `#pragma regswap` one or more times in a C or C++ source file with the following parameters:
@@ -22,14 +22,19 @@ where,
* `regB` - register to swapped with `regA` (r0-r31 or f0-f31)
* `startFile` - absolute address of the first function provided by this file (in hex)
+Invoke `#pragma iswap` with the following parameters:
+```
+#pragma iswap addrA addrB startFile
+```
+where,
+* `addrA` and `addrB` - absolute addresses of the instructions to be swapped
-Then, invoke the pragma processor with the `-fix-regswaps` option:
+Then, invoke the pragma processor:
```
-python3 pragma.py [-h] [-fix-regswaps] cc cflags output source
+python3 pragma.py [-h] cc cflags output source
```
## Future Work
-* Add support for an instruction-swap pragma (`#pragma iswap ...`)
* Support for more architectures besides Gekko/Broadway PowerPC
diff --git a/tools/pragma/pragma.py b/tools/pragma/pragma.py
index 75e0450..2a935a9 100644
--- a/tools/pragma/pragma.py
+++ b/tools/pragma/pragma.py
@@ -11,8 +11,7 @@
# regB: register to swap (r0-r31 or f0-f31)
# startFile: absolute address of the first function provided by this file (hex)
-# TODO: add support for an instruction swap pragma
-# TODO: add "#pragma startaddr <hex-addr>" to avoid rewriting the start address in each regswap pragma?
+#pragma iswap addrA addrB startFile
import os
import sys
@@ -138,6 +137,11 @@ misc_opcode_map = {
}
}
+class FloatInfo:
+ def __init__(self, is_float, int_regs):
+ self.is_float = is_float
+ self.int_regs = int_regs
+
class PPCInstr:
INSTR_SIZE = 32
@@ -184,20 +188,33 @@ class PPCInstr:
else:
return self.search_opcode_maps(opcode, misc_opcode_map)
+ def uses_float_regs(self):
+ op = self.get_opcode()
+ ext_op = self.get_ext_opcode()
+ if op in {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 60, 61}:
+ return FloatInfo(True, (11,))
+ elif (op == 4 and ext_op & 0x3F in {6, 7, 38, 39}) or (op == 31 and ext_op in {535, 567, 599, 631, 663, 695, 727, 759, 983}):
+ return FloatInfo(True, (11, 16))
+ elif op in {4, 59, 63}:
+ return FloatInfo(True, ())
+ return FloatInfo(False, ())
+
# edit the PPC instruction to swap the registers
def swap_registers(self, regA, regB):
- # DEBUG_v = hex(self.v)
+ info = self.uses_float_regs()
reg_fields = self.get_reg_fields()
- # print(str(reg_fields) + ", " + DEBUG_v)
- if reg_fields is None:
+ if not reg_fields:
return
for left in reg_fields:
right = left + self.REG_FIELD_SIZE - 1
currReg = self.get_field(left, right)
- if currReg == regA:
- self.set_field(left, right, regB)
- elif currReg == regB:
- self.set_field(left, right, regA)
+ # since r0-r31 occupy 0-31 and f0-31 occupy 32-63,
+ # subtract 32 from regA/regB if the next register field is for a floating point register
+ dec = 0 if not info.is_float or left in info.int_regs else -32
+ if currReg == regA + dec:
+ self.set_field(left, right, regB + dec)
+ elif currReg == regB + dec:
+ self.set_field(left, right, regA + dec)
parser = argparse.ArgumentParser()
@@ -209,15 +226,13 @@ parser.add_argument("output",
help="path to the outputted object file")
parser.add_argument("source",
help="path to the C/C++ source file")
-parser.add_argument("-fix-regswaps",
- help="execute #pragma regswap", action="store_true")
args = parser.parse_args()
def parse_reg(str):
if str[0] == 'r' or str[0] == 'f':
reg = int(str[1:])
if reg >= 0 and reg <= 31:
- return reg
+ return reg if str[0] == 'r' else reg + 32
raise ValueError("Failed to parse register argument (can be r0...r31 or f0...f31)")
class RegswapTask:
@@ -227,32 +242,51 @@ class RegswapTask:
self.regA = regA
self.regB = regB
+class IswapTask:
+ def __init__(self, src, dst):
+ self.src = src # .text section byte offset
+ self.dst = dst # .text section byte offset
+
regswap_tasks = []
+iswap_tasks = []
with open(args.source, "r") as src:
regswap_pattern = re.compile("[ \t]*#pragma[ \t]+regswap[ \t]+")
+ iswap_pattern = re.compile("[ \t]*#pragma[ \t]+iswap[ \t]+")
for line in src:
if regswap_pattern.match(line):
- if args.fix_regswaps:
- params = line.split()[2:]
- if len(params) != 5:
- raise ValueError("ERROR: " + len(params) + " arguments passed to #pragma regswap (expected 5)")
- start = int(params[0], base=16)
- end = int(params[1], base=16)
- regA = parse_reg(params[2])
- regB = parse_reg(params[3])
- start_file = int(params[4], base=16)
- if not (start % 4 == 0 and end % 4 == 0 and start_file % 4 == 0):
- raise ValueError("Invalid start, end, or start_file arguments (should have 4 byte aligment)")
- if not (start >= start_file and end > start):
- raise ValueError("Invalid start, end, or start_file arguments (end must be > start, and start >= start_file)")
- regswap_tasks.append(RegswapTask(start-start_file, end-start_file, regA, regB))
+ params = line.split()[2:]
+ if len(params) != 5:
+ raise ValueError("ERROR: " + str(len(params)) + " arguments passed to #pragma regswap (expected 5)")
+ start = int(params[0], base=16)
+ end = int(params[1], base=16)
+ regA = parse_reg(params[2])
+ regB = parse_reg(params[3])
+ start_file = int(params[4], base=16)
+ if not (start % 4 == 0 and end % 4 == 0 and start_file % 4 == 0):
+ raise ValueError("Invalid start, end, or start_file arguments (should have 4 byte aligment)")
+ if not (start >= start_file and end > start):
+ raise ValueError("Invalid start, end, or start_file arguments (end must be > start, and start >= start_file)")
+ regswap_tasks.append(RegswapTask(start-start_file, end-start_file, regA, regB))
+ elif iswap_pattern.match(line):
+ params = line.split()[2:]
+ if len(params) != 3:
+ raise ValueError("ERROR: " + str(len(params)) + " arguments passed to #pragma iswap (expected 3)")
+ src = int(params[0], base=16)
+ dst = int(params[1], base=16)
+ start_file = int(params[2], base=16)
+ if not (src % 4 == 0 and dst % 4 == 0 and start_file % 4 == 0):
+ raise ValueError("Invalid src, dst, or start_file arguments (should have 4 byte aligment)")
+ if not (src >= start_file and dst > src):
+ raise ValueError("Invalid src, dst, or start_file arguments (dst must be > src, and src >= start_file)")
+ iswap_tasks.append(IswapTask(src-start_file, dst-start_file))
+
subprocess.run([*args.cc.strip().split(' '), *args.cflags.split(' '), "-o", args.output, args.source])
instrs = []
TEXT_INDEX = 1 # NOTE: assumes that mwcceppc always places the .text section header at index 1
SHDR_32_SIZE = 40 # size of an Elf32_Shdr object
-if args.fix_regswaps and len(regswap_tasks) != 0:
+if regswap_tasks or iswap_tasks:
with open(args.output, "rb") as f:
if f.read(7) != b'\x7FELF\x01\x02\x01':
raise ValueError("compiler output is not an current version ELF file for a 32-bit big endian architecture")
@@ -279,6 +313,14 @@ if args.fix_regswaps and len(regswap_tasks) != 0:
raise ValueError("End address " + (task.end + start_file) + " is past the end of the ELF file's .text section")
for i in range(task.start // 4, task.end // 4):
instrs[i].swap_registers(task.regA, task.regB)
+
+ # perform iswap tasks
+ for task in iswap_tasks:
+ if task.dst > text_size:
+ raise ValueError("End address " + (task.dst + start_file) + " is past the end of the ELF file's .text section")
+ a = task.src // 4
+ b = task.dst // 4
+ instrs[a], instrs[b] = instrs[b], instrs[a]
# write patched .text section back to the ELF
with open(args.output, "rb+") as f: