diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | tools/pragma/README.md | 19 | ||||
-rw-r--r-- | tools/pragma/pragma.py | 96 |
3 files changed, 83 insertions, 36 deletions
@@ -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: |