diff --git a/README.md b/README.md index 7e84ecf55..556a7f592 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Design: - Workflow for development: - `./gc.sh` : run the compiler in interactive mode - `./gs.sh` : run a goos interpreter in interactive mode - - `./decomp.sh ./iso_data` : run the decompiler + - `./decomp.sh : run the decompiler Current state: - GOAL compiler just implements the GOOS Scheme Macro Language. Running `./gc.sh` just loads the GOOS library (`goalc/gs/goos-lib.gs`) and then goes into an interactive mode. Use `(exit)` to exit. @@ -79,7 +79,8 @@ TODOS: - performance stats for `SystemThread` (probably just get rid of these performance stats completely) - `mmap`ing executable memory - line input library (appears windows compatible?) -- Clean up possible duplicate code in compiler/decompiler `util` folder +- Clean up possible duplicate code in compiler/decompiler `util` folder, consider a common utility library +- Clean up header guard names (or just use `#pragma once`?) - Investigate a better config format - The current JSON library seems to have issues with comments, which I really like - Clean up use of namespaces @@ -90,9 +91,9 @@ TODOS: - Listener protocol document - GOAL Compiler IR - GOAL Compiler Skeleton - -In Progress: -- GOAL emitter / emitter testing setup +- Gtest setup for checking decompiler results against hand-decompiled stuff +- Clean up decompiler print spam, finish up the CFG stuff +- Decompiler document Project Description diff --git a/decomp.sh b/decomp.sh new file mode 100755 index 000000000..947ec9bf7 --- /dev/null +++ b/decomp.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Directory of this script +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +$DIR/build/decompiler/decompiler $DIR/decompiler/config/jak1_ntsc_black_label.jsonc $DIR/iso_data $DIR/decompiler_out \ No newline at end of file diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index e69de29bb..16aecbbc3 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -0,0 +1,25 @@ +add_executable(decompiler + util/LispPrint.cpp + main.cpp + ObjectFile/ObjectFileDB.cpp + Disasm/Instruction.cpp + Disasm/InstructionDecode.cpp + Disasm/OpcodeInfo.cpp + Disasm/Register.cpp + ObjectFile/LinkedObjectFileCreation.cpp + ObjectFile/LinkedObjectFile.cpp + Function/Function.cpp + util/FileIO.cpp + config.cpp + util/LispPrint.cpp + util/Timer.cpp + Function/BasicBlocks.cpp + Disasm/InstructionMatching.cpp + TypeSystem/GoalType.cpp + TypeSystem/GoalFunction.cpp + TypeSystem/GoalSymbol.cpp + TypeSystem/TypeInfo.cpp + TypeSystem/TypeSpec.cpp Function/CfgVtx.cpp Function/CfgVtx.h) + +target_link_libraries(decompiler + minilzo) \ No newline at end of file diff --git a/decompiler/Disasm/Instruction.cpp b/decompiler/Disasm/Instruction.cpp new file mode 100644 index 000000000..59cadb9bf --- /dev/null +++ b/decompiler/Disasm/Instruction.cpp @@ -0,0 +1,304 @@ +/*! + * @file Instruction.cpp + * An EE instruction, represented as an operation, plus a list of source/destination atoms. + * Can print itself (within the context of a LinkedObjectFile). + */ + +#include "Instruction.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include + +/*! + * Convert atom to a string for disassembly. + */ +std::string InstructionAtom::to_string(const LinkedObjectFile& file) const { + switch (kind) { + case REGISTER: + return reg.to_string(); + case IMM: + return std::to_string(imm); + case LABEL: + return file.get_label_name(label_id); + case VU_ACC: + return "acc"; + case VU_Q: + return "Q"; + case IMM_SYM: + return sym; + default: + assert(false); + } +} + +/*! + * Make this atom a register. + */ +void InstructionAtom::set_reg(Register r) { + kind = REGISTER; + reg = r; +} + +/*! + * Make this atom an immediate. + */ +void InstructionAtom::set_imm(int32_t i) { + kind = IMM; + imm = i; +} + +/*! + * Make this atom a label. + */ +void InstructionAtom::set_label(int id) { + kind = LABEL; + label_id = id; +} + +/*! + * Make this atom the VU ACC register. + */ +void InstructionAtom::set_vu_acc() { + kind = VU_ACC; +} + +/*! + * Make this atom the VU0 Q register. + */ +void InstructionAtom::set_vu_q() { + kind = VU_Q; +} + +/*! + * Make this atom a symbol. + */ +void InstructionAtom::set_sym(std::string _sym) { + kind = IMM_SYM; + sym = std::move(_sym); +} + +/*! + * Get as register, or error if not a register. + */ +Register InstructionAtom::get_reg() const { + assert(kind == REGISTER); + return reg; +} + +/*! + * Get as integer immediate, or error if not an integer immediate. + */ +int32_t InstructionAtom::get_imm() const { + assert(kind == IMM); + return imm; +} + +/*! + * Get as label index, or error if not a label. + */ +int InstructionAtom::get_label() const { + assert(kind == LABEL); + return label_id; +} + +/*! + * Get as symbol, or error if not a symbol. + */ +std::string InstructionAtom::get_sym() const { + assert(kind == IMM_SYM); + return sym; +} + +/*! + * True if this atom is some sort of constant that doesn't involve linking. + */ +bool InstructionAtom::is_link_or_label() const { + return kind == IMM_SYM || kind == LABEL; +} + +/*! + * Convert entire instruction to a string. + */ +std::string Instruction::to_string(const LinkedObjectFile& file) const { + auto& info = gOpcodeInfo[(int)kind]; + + // the name + std::string result = info.name; + + // optional "interlock" specification. + if (il != 0xff) { + result.append(il ? ".i" : ".ni"); + } + + // optional "broadcast" specification for COP2 opcodes. + if (cop2_bc != 0xff) { + switch (cop2_bc) { + case 0: + result.push_back('x'); + break; + case 1: + result.push_back('y'); + break; + case 2: + result.push_back('z'); + break; + case 3: + result.push_back('w'); + break; + default: + result.push_back('?'); + break; + } + } + + // optional "destination" specification for COP2 opcodes. + if (cop2_dest != 0xff) { + result += "."; + if (cop2_dest & 8) + result.push_back('x'); + if (cop2_dest & 4) + result.push_back('y'); + if (cop2_dest & 2) + result.push_back('z'); + if (cop2_dest & 1) + result.push_back('w'); + } + + // relative store and load instructions have a special syntax in MIPS + if (info.is_store) { + assert(n_dst == 0); + assert(n_src == 3); + result += " "; + result += src[0].to_string(file); + result += ", "; + result += src[1].to_string(file); + result += "("; + result += src[2].to_string(file); + result += ")"; + } else if (info.is_load) { + assert(n_dst == 1); + assert(n_src == 2); + result += " "; + result += dst[0].to_string(file); + result += ", "; + result += src[0].to_string(file); + result += "("; + result += src[1].to_string(file); + result += ")"; + } else { + // for instructions that aren't a store or load, the dest/sources are comma separated. + bool end_comma = false; + + for (uint8_t i = 0; i < n_dst; i++) { + result += " " + dst[i].to_string(file) + ","; + end_comma = true; + } + + for (uint8_t i = 0; i < n_src; i++) { + result += " " + src[i].to_string(file) + ","; + end_comma = true; + } + + if (end_comma) { + result.pop_back(); + } + } + + return result; +} + +/*! + * Was this instruction successfully decoded? + */ +bool Instruction::is_valid() const { + return kind != InstructionKind::UNKNOWN; +} + +/*! + * Add a destination atom to this Instruction + */ +void Instruction::add_dst(InstructionAtom& a) { + assert(n_dst < MAX_INTRUCTION_DEST); + dst[n_dst++] = a; +} + +/*! + * Add a source atom to this Instruction + */ +void Instruction::add_src(InstructionAtom& a) { + assert(n_src < MAX_INSTRUCTION_SOURCE); + src[n_src++] = a; +} + +/*! + * Get a source atom that's an immediate, or error if it doesn't exist. + */ +InstructionAtom& Instruction::get_imm_src() { + for (int i = 0; i < n_src; i++) { + if (src[i].kind == InstructionAtom::IMM) { + return src[i]; + } + } + assert(false); + return src[0]; +} + +/*! + * Try to find a src which is an integer immediate, and return it as an integer. + */ +int32_t Instruction::get_imm_src_int() { + return get_imm_src().get_imm(); +} + +/*! + * Safe get dst atom + */ +InstructionAtom& Instruction::get_dst(size_t idx) { + assert(idx < n_dst); + return dst[idx]; +} + +/*! + * Safe get src atom + */ +InstructionAtom& Instruction::get_src(size_t idx) { + assert(idx < n_src); + return src[idx]; +} + +/*! + * Safe get dst atom + */ +const InstructionAtom& Instruction::get_dst(size_t idx) const { + assert(idx < n_dst); + return dst[idx]; +} + +/*! + * Safe get src atom + */ +const InstructionAtom& Instruction::get_src(size_t idx) const { + assert(idx < n_src); + return src[idx]; +} + +/*! + * Get OpcodeInfo for the opcode used in this instruction. + */ +const OpcodeInfo& Instruction::get_info() const { + return gOpcodeInfo[int(kind)]; +} + +/*! + * Get the target label for this instruction. If the instruction doesn't have a target label, + * return -1. + */ +int Instruction::get_label_target() const { + int result = -1; + for (int i = 0; i < n_src; i++) { + if (src[i].kind == InstructionAtom::AtomKind::LABEL) { + assert(result == -1); + result = src[i].get_label(); + } + } + return result; +} diff --git a/decompiler/Disasm/Instruction.h b/decompiler/Disasm/Instruction.h new file mode 100644 index 000000000..bdf999bcd --- /dev/null +++ b/decompiler/Disasm/Instruction.h @@ -0,0 +1,89 @@ +/*! + * @file Instruction.h + * An EE instruction, represented as an operation, plus a list of source/destination atoms. + * Can print itself (within the context of a LinkedObjectFile). + */ + +#ifndef NEXT_INSTRUCTION_H +#define NEXT_INSTRUCTION_H + +#include "OpcodeInfo.h" +#include "Register.h" + +class LinkedObjectFile; + +constexpr int MAX_INSTRUCTION_SOURCE = 3; +constexpr int MAX_INTRUCTION_DEST = 1; + +// An "atom", representing a single register, immediate, etc... for use in an Instruction. +struct InstructionAtom { + enum AtomKind { + REGISTER, // An EE Register + IMM, // An immediate value (stored as int32) + IMM_SYM, // An immediate value (a symbolic link) + LABEL, // A label in a LinkedObjectFile + VU_ACC, // The VU0 Accumulator + VU_Q, // The VU0 Q Register + INVALID + } kind = INVALID; + + void set_reg(Register r); + void set_imm(int32_t i); + void set_label(int id); + void set_vu_q(); + void set_vu_acc(); + void set_sym(std::string _sym); + + Register get_reg() const; + int32_t get_imm() const; + int get_label() const; + std::string get_sym() const; + + std::string to_string(const LinkedObjectFile& file) const; + + bool is_link_or_label() const; + + private: + int32_t imm; + int label_id; + Register reg; + + std::string sym; +}; + +// An "Instruction", consisting of a "kind" (the opcode), and the source/destination atoms it +// operates on. +class Instruction { + public: + InstructionKind kind = InstructionKind::UNKNOWN; + + std::string to_string(const LinkedObjectFile& file) const; + bool is_valid() const; + + void add_src(InstructionAtom& a); + void add_dst(InstructionAtom& a); + + InstructionAtom& get_src(size_t idx); + InstructionAtom& get_dst(size_t idx); + const InstructionAtom& get_src(size_t idx) const; + const InstructionAtom& get_dst(size_t idx) const; + + // source and destination atoms + uint8_t n_src = 0, n_dst = 0; + InstructionAtom src[MAX_INSTRUCTION_SOURCE]; + InstructionAtom dst[MAX_INTRUCTION_DEST]; + + InstructionAtom& get_imm_src(); + int32_t get_imm_src_int(); + + const OpcodeInfo& get_info() const; + + int get_label_target() const; + + // extra fields for some COP2 instructions. + uint8_t cop2_dest = 0xff; // 0xff indicates "don't print dest" + uint8_t cop2_bc = 0xff; // 0xff indicates "don't print bc" + uint8_t il = 0xff; // 0xff indicates "don't print il" +}; + +#endif // NEXT_INSTRUCTION_H diff --git a/decompiler/Disasm/InstructionDecode.cpp b/decompiler/Disasm/InstructionDecode.cpp new file mode 100644 index 000000000..6fb1b6571 --- /dev/null +++ b/decompiler/Disasm/InstructionDecode.cpp @@ -0,0 +1,1173 @@ +/*! + * @file InstructionDecode.cpp + * The Instruction Decoder - converts a LinkedWord into a Instruction. + * This is the part of the disassembler that decodes MIPS instructions. + */ + +#include "InstructionDecode.h" +#include +#include "decompiler/ObjectFile/LinkedObjectFile.h" + +// utility class to extract fields of an opcode. +struct OpcodeFields { + OpcodeFields(uint32_t _data) : data(_data) {} + + // 26 - 31 + uint32_t op() { return (data >> 26); } + + ////////////// + // R - Type // + ////////////// + + // 21 - 25 + uint32_t rs() { return (data >> 21) & 0x1f; } + + // 16 - 20 + uint32_t rt() { return (data >> 16) & 0x1f; } + + // 11 - 15 + uint32_t rd() { return (data >> 11) & 0x1f; } + + // 6 - 10 + uint32_t sa() { return (data >> 6) & 0x1f; } + + // 0 - 5 + uint32_t function() { return (data)&0x3f; } + + //////////////// + // Immediates // + //////////////// + + int32_t simm16() { return (int16_t)(data); } + + int32_t zimm16() { return (uint16_t)(data); } + + uint32_t imm5() { return (data >> 6) & 0x1f; } + + uint32_t imm15() { return (data >> 6) & 0b111111111111111; } + + //////////////////// + // Floating Point // + //////////////////// + + uint32_t cop_func() { return (data >> 21) & 0x1f; } + + uint32_t ft() { return (data >> 16) & 0x1f; } + + uint32_t fs() { return (data >> 11) & 0x1f; } + + uint32_t fd() { return (data >> 6) & 0x1f; } + + //////////// + // Others // + //////////// + + uint32_t pcreg() { return (data >> 1) & 0x1f; } + + uint32_t syscall() { return (data >> 6) & 0xfffff; } + + uint32_t MMI_func() { return (data >> 6) & 0x1f; } + + uint32_t lower11() { return (uint32_t)(data & 0x7ff); } + + uint32_t lower6() { return (uint32_t)(data & 0b111111); } + + uint32_t dest() { return (data >> 21) & 0b1111; } + + uint32_t data; +}; + +////////////////// +// OPCODE DECODE +////////////////// + +static InstructionKind decode_cop2(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.lower11()) { + case 0b0: + case 0b1: + switch (fields.cop_func()) { + case 0b00001: + return IK::QMFC2; + + case 0b00101: + assert(((fields.data >> 1) & (0b1111111111)) == 0); + return IK::QMTC2; + + case 0b00010: + return IK::CFC2; + + case 0b00110: + return IK::CTC2; + + default: + return IK::UNKNOWN; + } + break; + + case 0b00010111100: + case 0b00010111101: + case 0b00010111110: + case 0b00010111111: + assert(fields.data & (1 << 25)); + return IK::VMADDA_BC; + + case 0b00000111100: + case 0b00000111101: + case 0b00000111110: + case 0b00000111111: + assert(fields.data & (1 << 25)); + return IK::VADDA_BC; + + case 0b00110111100: + case 0b00110111101: + case 0b00110111110: + case 0b00110111111: + assert(fields.data & (1 << 25)); + return IK::VMULA_BC; + + case 0b01010111110: + assert(fields.data & (1 << 25)); + return IK::VMULA; + + case 0b01010111100: + assert(fields.data & (1 << 25)); + return IK::VADDA; + + case 0b01010111101: + assert(fields.data & (1 << 25)); + return IK::VMADDA; + + case 0b00011111100: + case 0b00011111101: + case 0b00011111110: + case 0b00011111111: + assert(fields.data & (1 << 25)); + return IK::VMSUBA_BC; + + case 0b00101111100: + assert(fields.data & (1 << 25)); + return IK::VFTOI0; + + case 0b00101111101: + assert(fields.data & (1 << 25)); + return IK::VFTOI4; + + case 0b00101111110: + assert(fields.data & (1 << 25)); + return IK::VFTOI12; + + case 0b00100111100: + assert(fields.data & (1 << 25)); + return IK::VITOF0; + + case 0b00100111110: + assert(fields.data & (1 << 25)); + return IK::VITOF12; + + case 0b00100111111: + assert(fields.data & (1 << 25)); + return IK::VITOF15; + + case 0b00111111100: + assert(fields.data & (1 << 25)); + assert(fields.ft() == 0); + return IK::VMULAQ; + + case 0b00111111101: + assert(fields.data & (1 << 25)); + return IK::VABS; + + case 0b00111111111: + assert(fields.data & (1 << 25)); + assert(fields.dest() == 0b1110); + return IK::VCLIP; + + case 0b01011111111: + assert(fields.dest() == 0); + assert(fields.ft() == 0); + assert(fields.fs() == 0); + return IK::VNOP; + case 0b01101111101: + assert(fields.data & (1 << 25)); + return IK::VSQI; + case 0b01101111100: + assert(fields.data & (1 << 25)); + return IK::VLQI; + case 0b01110111111: + assert(fields.dest() == 0); + assert(fields.ft() == 0); + assert(fields.fs() == 0); + return IK::VWAITQ; + + case 0b01011111110: + assert(fields.dest() == 0b1110); + assert(fields.data & (1 << 25)); + return IK::VOPMULA; + + case 0b01100111100: + assert(fields.data & (1 << 25)); + return IK::VMOVE; + + case 0b01110111100: + assert(fields.data & (1 << 25)); + return IK::VDIV; + + case 0b01110111101: + assert(fields.fs() == 0); + assert(((fields.data >> 21) & 3) == 0); + assert(fields.data & (1 << 25)); + return IK::VSQRT; + + case 0b01111111100: + assert(((fields.data >> 23) & 3) == 0); + assert(fields.data & (1 << 25)); + return IK::VMTIR; + + case 0b01110111110: + assert(fields.data & (1 << 25)); + return IK::VRSQRT; + + case 0b10000111100: + assert(fields.fs() == 0); + assert(fields.data & (1 << 25)); + return IK::VRNEXT; + + case 0b10000111101: + assert(fields.fs() == 0); + assert(fields.data & (1 << 25)); + return IK::VRGET; + + case 0b10000111111: + assert(fields.ft() == 0); + assert(fields.data & (1 << 25)); + assert(((fields.data >> 23) & 3) == 0); + return IK::VRXOR; + default: + + switch (fields.lower6()) { + case 0b000000: + case 0b000001: + case 0b000010: + case 0b000011: + assert(fields.data & (1 << 25)); + return IK::VADD_BC; + + case 0b000100: + case 0b000101: + case 0b000110: + case 0b000111: + assert(fields.data & (1 << 25)); + return IK::VSUB_BC; + + case 0b001000: + case 0b001001: + case 0b001010: + case 0b001011: + assert(fields.data & (1 << 25)); + return IK::VMADD_BC; + + case 0b001100: + case 0b001101: + case 0b001110: + case 0b001111: + assert(fields.data & (1 << 25)); + return IK::VMSUB_BC; + + case 0b010000: + case 0b010001: + case 0b010010: + case 0b010011: + assert(fields.data & (1 << 25)); + return IK::VMAX_BC; + + case 0b010100: + case 0b010101: + case 0b010110: + case 0b010111: + assert(fields.data & (1 << 25)); + return IK::VMINI_BC; + + case 0b011000: + case 0b011001: + case 0b011010: + case 0b011011: + assert(fields.data & (1 << 25)); + return IK::VMUL_BC; + + case 0b011100: + assert(fields.ft() == 0); + assert(fields.data & (1 << 25)); + return IK::VMULQ; + + case 0b100000: + assert(fields.data & (1 << 25)); + return IK::VADDQ; + + case 0b100100: + assert(fields.data & (1 << 25)); + return IK::VSUBQ; + + case 0b100101: + assert(fields.ft() == 0); + assert(fields.data & (1 << 25)); + return IK::VMSUBQ; + + case 0b101000: + assert(fields.data & (1 << 25)); + return IK::VADD; + case 0b101001: + assert(fields.data & (1 << 25)); + return IK::VMADD; + case 0b101010: + assert(fields.data & (1 << 25)); + return IK::VMUL; + case 0b101011: + assert(fields.data & (1 << 25)); + return IK::VMAX; + case 0b101100: + assert(fields.data & (1 << 25)); + return IK::VSUB; + case 0b101101: + assert(fields.data & (1 << 25)); + return IK::VMSUB; + case 0b101110: + assert(fields.data & (1 << 25)); + assert(fields.dest() == 0b1110); + return IK::VOPMSUB; + case 0b101111: + assert(fields.data & (1 << 25)); + return IK::VMINI; + case 0b110010: + assert(fields.data & (1 << 25)); + assert(fields.dest() == 0b0); + return IK::VIADDI; + case 0b110100: + assert(fields.data & (1 << 25)); + assert(fields.dest() == 0b0); + return IK::VIAND; + case 0b111000: + assert(fields.data & (1 << 25)); + assert(fields.dest() == 0b0); + return IK::VCALLMS; + default: + return IK::UNKNOWN; + } + } +} + +static InstructionKind decode_W(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.function()) { + case 0b100000: + assert(fields.ft() == 0); + return IK::CVTSW; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_S(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.function()) { + case 0b000000: + return IK::ADDS; + case 0b000001: + return IK::SUBS; + case 0b000010: + return IK::MULS; + case 0b000011: + return IK::DIVS; + case 0b000101: + return IK::ABSS; + case 0b000110: + assert(fields.ft() == 0); + return IK::MOVS; + case 0b000111: + assert(fields.ft() == 0); + return IK::NEGS; + case 0b000100: + assert(fields.fs() == 0); + return IK::SQRTS; + case 0b010110: + return IK::RSQRTS; + case 0b011000: + assert(fields.fd() == 0); + return IK::ADDAS; + case 0b011010: + assert(fields.fd() == 0); + return IK::MULAS; + case 0b011100: + return IK::MADDS; + case 0b011101: + return IK::MSUBS; + case 0b011110: + assert(fields.fd() == 0); + return IK::MADDAS; + case 0b011111: + assert(fields.fd() == 0); + return IK::MSUBAS; + case 0b100100: + assert(fields.ft() == 0); + return IK::CVTWS; + case 0b101000: + return IK::MAXS; + case 0b101001: + return IK::MINS; + case 0b110010: + assert(fields.fd() == 0); + return IK::CEQS; + case 0b110100: + assert(fields.fd() == 0); + return IK::CLTS; + case 0b110110: + assert(fields.fd() == 0); + return IK::CLES; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_BC1(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.ft()) { + case 0b00000: + return IK::BC1F; + case 0b00001: + return IK::BC1T; + case 0b00010: + return IK::BC1FL; + case 0b00011: + return IK::BC1TL; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_cop1(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.cop_func()) { + case 0b00000: + assert(fields.sa() == 0); + assert(fields.function() == 0); + return IK::MFC1; + case 0b00100: + assert(fields.sa() == 0); + assert(fields.function() == 0); + return IK::MTC1; + case 0b01000: + return decode_BC1(fields); + case 0b10000: + return decode_S(fields); + case 0b10100: + return decode_W(fields); + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_c0(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.function()) { + case 0b011000: + return IK::ERET; + case 0b111000: + assert(fields.sa() == 0 && fields.rd() == 0 && fields.rt() == 0); + return IK::EI; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_mt0(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.lower11()) { + case 0b00000000000: + return IK::MTC0; + case 0b00000000100: + assert(fields.rd() == 0b11000); + return IK::MTDAB; + case 0b00000000101: + assert(fields.rd() == 0b11000); + return IK::MTDABM; + default: + if (fields.rd() == 0b11001 && fields.sa() == 0 && (fields.data & 1) == 1) { + return IK::MTPC; + } else { + return IK::UNKNOWN; + } + } +} + +static InstructionKind decode_mf0(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.lower11()) { + case 0b0: + return IK::MFC0; + default: + if (fields.rd() == 0b11001 && fields.sa() == 0 && (fields.data & 1) == 1) { + return IK::MFPC; + } else { + return IK::UNKNOWN; + } + } +} + +static InstructionKind decode_cop0(OpcodeFields fields) { + switch (fields.cop_func()) { + case 0b00000: + return decode_mf0(fields); + case 0b00100: + return decode_mt0(fields); + case 0b10000: + return decode_c0(fields); + default: + return InstructionKind::UNKNOWN; + } +} + +static InstructionKind decode_mmi3(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.MMI_func()) { + case 0b01010: + return IK::PINTEH; + case 0b01110: + return IK::PCPYUD; + case 0b10010: + return IK::POR; + case 0b10011: + return IK::PNOR; + case 0b11011: + assert(fields.rs() == 0); + return IK::PCPYH; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_mmi2(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.MMI_func()) { + case 0b01110: + return IK::PCPYLD; + case 0b10000: + return IK::PMADDH; + case 0b10010: + return IK::PAND; + case 0b11100: + return IK::PMULTH; + case 0b11110: + return IK::PEXEW; + case 0b11111: + return IK::PROT3W; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_mmi1(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.MMI_func()) { + case 0b00001: + return IK::PABSW; + case 0b00010: + return IK::PCEQW; + case 0b00011: + return IK::PMINW; + case 0b00111: + return IK::PMINH; + case 0b01010: + return IK::PCEQB; + case 0b10010: + return IK::PEXTUW; + case 0b10110: + return IK::PEXTUH; + case 0b11010: + return IK::PEXTUB; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_mmi0(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.MMI_func()) { + case 0b00000: + return IK::PADDW; + case 0b00001: + return IK::PSUBW; + case 0b00010: + return IK::PCGTW; + case 0b00011: + return IK::PMAXW; + case 0b00100: + return IK::PADDH; + case 0b00111: + return IK::PMAXH; + case 0b10010: + return IK::PEXTLW; + case 0b10011: + return IK::PPACW; + case 0b10111: + return IK::PPACH; + case 0b10110: + return IK::PEXTLH; + case 0b11010: + return IK::PEXTLB; + case 0b11011: + return IK::PPACB; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_pmfhl(OpcodeFields fields) { + // the PMFHL instruction is split into several types, and we create different instructions for + // each. + typedef InstructionKind IK; + switch (fields.sa()) { + case 0b00001: + assert(fields.rs() == 0); + assert(fields.rt() == 0); + return IK::PMFHL_UW; + case 0b00000: + assert(fields.rs() == 0); + assert(fields.rt() == 0); + return IK::PMFHL_LW; + case 0b00011: + assert(fields.rs() == 0); + assert(fields.rt() == 0); + return IK::PMFHL_LH; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_mmi(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.function()) { + case 0b000100: + assert(fields.sa() == 0); + assert(fields.rt() == 0); + return IK::PLZCW; + case 0b001000: + return decode_mmi0(fields); + case 0b001001: + return decode_mmi2(fields); + + case 0b010011: + assert(fields.sa() == 0); + assert(fields.rd() == 0); + assert(fields.rt() == 0); + return IK::MTLO1; + case 0b010010: + assert(fields.sa() == 0); + assert(fields.rs() == 0); + assert(fields.rt() == 0); + return IK::MFLO1; + + case 0b101000: + return decode_mmi1(fields); + case 0b101001: + return decode_mmi3(fields); + case 0b110000: + return decode_pmfhl(fields); + case 0b110100: + return IK::PSLLH; + case 0b110110: + return IK::PSRLH; + case 0b110111: + return IK::PSRAH; + case 0b111100: + return IK::PSLLW; + case 0b111111: + return IK::PSRAW; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_regimm(OpcodeFields files) { + typedef InstructionKind IK; + switch (files.rt()) { + case 0b00000: + return IK::BLTZ; + case 0b00001: + return IK::BGEZ; + case 0b00010: + return IK::BLTZL; + case 0b00011: + return IK::BGEZL; + case 0b10001: + return IK::BGEZAL; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_sync(OpcodeFields fields) { + // the "sync" opcode has a "stype" field which picks between P and L type syncs. + // to avoid implementing this, we just split SYNC into two separate instructions. + typedef InstructionKind IK; + auto stype = fields.sa(); + assert(fields.rt() == 0); + assert(fields.rs() == 0); + assert(fields.rd() == 0); + if (stype == 0b00000) { + return IK::SYNCL; + } else if (stype == 0b10000) { + return IK::SYNCP; + } else { + return IK::UNKNOWN; + } +} + +static InstructionKind decode_special(OpcodeFields fields) { + typedef InstructionKind IK; + switch (fields.function()) { + case 0b000000: + assert(fields.rs() == 0); + return IK::SLL; + // RESERVED + case 0b000010: + assert(fields.rs() == 0); + return IK::SRL; + case 0b000011: + assert(fields.rs() == 0); + return IK::SRA; + case 0b000100: + assert(fields.sa() == 0); + return IK::SLLV; + // RESERVED + // SRLV + // SRAV + case 0b001000: + assert(fields.sa() == 0); + assert(fields.rd() == 0); + assert(fields.rt() == 0); + return IK::JR; + case 0b001001: + assert(fields.rt() == 0); + assert(fields.sa() == 0); + return IK::JALR; + case 0b001010: + assert(fields.sa() == 0); + return IK::MOVZ; + case 0b001011: + assert(fields.sa() == 0); + return IK::MOVN; + case 0b001100: + return IK::SYSCALL; + // BREAK + // RESERVED + case 0b001111: + return decode_sync(fields); + + case 0b010000: + assert(fields.rs() == 0); + assert(fields.rt() == 0); + assert(fields.sa() == 0); + return IK::MFHI; + // MTHI + case 0b010010: + assert(fields.rs() == 0); + assert(fields.rt() == 0); + assert(fields.sa() == 0); + return IK::MFLO; + // MTLO + case 0b010100: + assert(fields.sa() == 0); + return IK::DSLLV; + // RESERVED + case 0b010110: + assert(fields.sa() == 0); + return IK::DSRLV; + case 0b010111: + assert(fields.sa() == 0); + return IK::DSRAV; + case 0b011000: + assert(fields.sa() == 0); + return IK::MULT3; + case 0b011001: + assert(fields.sa() == 0); + return IK::MULTU3; + case 0b011010: + assert(fields.sa() == 0); + assert(fields.rd() == 0); + return IK::DIV; + case 0b011011: + assert(fields.sa() == 0); + assert(fields.rd() == 0); + return IK::DIVU; + // 4x UNSUPPORTED + // ADD + case 0b100001: + assert(fields.sa() == 0); + return IK::ADDU; + // SUB + case 0b100011: + assert(fields.sa() == 0); + return IK::SUBU; + case 0b100100: + assert(fields.sa() == 0); + return IK::AND; + case 0b100101: + assert(fields.sa() == 0); + return IK::OR; + case 0b100110: + assert(fields.sa() == 0); + return IK::XOR; + case 0b100111: + assert(fields.sa() == 0); + return IK::NOR; + // MFSA + // MTSA + case 0b101010: + assert(fields.sa() == 0); + return IK::SLT; + case 0b101011: + assert(fields.sa() == 0); + return IK::SLTU; + // DADD + case 0b101101: + return IK::DADDU; + // DSUB + case 0b101111: + return IK::DSUBU; + // TGE + // TGEU + // TLT + // TLTU + // TEQ + // RESERVED + // TNE + // RESERVED + case 0b111000: + assert(fields.rs() == 0); + return IK::DSLL; + // RESERVED + case 0b111010: + assert(fields.rs() == 0); + return IK::DSRL; + case 0b111011: + assert(fields.rs() == 0); + return IK::DSRA; + case 0b111100: + assert(fields.rs() == 0); + return IK::DSLL32; + // RESERVED + case 0b111110: + assert(fields.rs() == 0); + return IK::DSRL32; + case 0b111111: + assert(fields.rs() == 0); + return IK::DSRA32; + default: + return IK::UNKNOWN; + } +} + +static InstructionKind decode_cache(OpcodeFields fields) { + typedef InstructionKind IK; + // there's only one cache instruction used (DXWBIN), so we just use a CACHE DXWBIN instruction + // to avoid having to implement the full cache instruction decoding. + switch (fields.rt()) { + case 0b10100: + return IK::CACHE_DXWBIN; + default: + return IK::UNKNOWN; + } +} + +/*! + * Top level opcode decode + */ +static InstructionKind decode_opcode(uint32_t code) { + OpcodeFields fields(code); + typedef InstructionKind IK; + switch (fields.op()) { + case 0b000000: + return decode_special(fields); + case 0b000001: + return decode_regimm(fields); + // J 010 + // JAL 011 + case 0b000100: + return IK::BEQ; + case 0b000101: + return IK::BNE; + case 0b000110: + return IK::BLEZ; + case 0b000111: + return IK::BGTZ; + // ADDI 1000 + case 0b001001: + return IK::ADDIU; + case 0b001010: + return IK::SLTI; + case 0b001011: + return IK::SLTIU; + case 0b001100: + return IK::ANDI; + case 0b001101: + return IK::ORI; + case 0b001110: + return IK::XORI; + case 0b001111: + assert(fields.rs() == 0); + return IK::LUI; + case 0b010000: + return decode_cop0(fields); + case 0b010001: + return decode_cop1(fields); + case 0b010010: + return decode_cop2(fields); + // 010011: + // reserved + case 0b010100: + return IK::BEQL; + case 0b010101: + return IK::BNEL; + // 010110 + // blezl + case 0b010111: + assert(fields.rt() == 0); + return IK::BGTZL; + // 0b011000: + // daddi + case 0b011001: + return IK::DADDIU; + case 0b011010: + return IK::LDL; + case 0b011011: + return IK::LDR; + case 0b011100: + return decode_mmi(fields); + // 0b011101: + // reserved + case 0b011110: + return IK::LQ; + case 0b011111: + return IK::SQ; + case 0b100000: + return IK::LB; + case 0b100001: + return IK::LH; + case 0b100010: + return IK::LWL; + case 0b100011: + return IK::LW; + case 0b100100: + return IK::LBU; + case 0b100101: + return IK::LHU; + case 0b100110: + return IK::LWR; + case 0b100111: + return IK::LWU; + case 0b101000: + return IK::SB; + case 0b101001: + return IK::SH; + case 0b101011: + return IK::SW; + // SDL + // SDR + // SWR + case 0b101111: + return decode_cache(fields); + + // unsupported + case 0b110001: + return IK::LWC1; + // unsupported + case 0b110011: + return IK::PREF; + // unsupported + // unsupported + case 0b110110: + return IK::LQC2; + case 0b110111: + return IK::LD; + case 0b111001: + return IK::SWC1; + case 0b111110: + return IK::SQC2; + case 0b111111: + return IK::SD; + default: + return IK::UNKNOWN; + break; + } +} + +/*! + * Top level decode function. + */ +Instruction decode_instruction(LinkedWord& word, LinkedObjectFile& file, int seg_id, int word_id) { + // determine the opcode, and get info for it + Instruction i; + auto op = decode_opcode(word.data); + auto& info = gOpcodeInfo[(int)op]; + if (!info.defined) { + return i; + } + i.kind = op; + OpcodeFields fields(word.data); + + // loop through decoding steps to extract a value + for (int j = 0; j < info.step_count; j++) { + auto& step = info.steps[j]; + int32_t value; + switch (step.field) { + case FieldType::RS: + value = fields.rs(); + break; + case FieldType::RT: + value = fields.rt(); + break; + case FieldType::RD: + value = fields.rd(); + break; + case FieldType::FT: + value = fields.ft(); + break; + case FieldType::FS: + value = fields.fs(); + break; + case FieldType::FD: + value = fields.fd(); + break; + case FieldType::SIMM16: + value = fields.simm16(); + break; + case FieldType::ZIMM16: + value = fields.zimm16(); + break; + case FieldType::SA: + value = fields.sa(); + break; + case FieldType::SYSCALL: + value = fields.syscall(); + break; + case FieldType::PCR: + value = fields.pcreg(); + break; + case FieldType::DEST: + value = fields.dest(); + break; + case FieldType::BC: + value = fields.data & 0b11; + break; + case FieldType::IMM5: + value = fields.imm5(); + break; + case FieldType::IL: + value = fields.data & 1; + break; + case FieldType::IMM15: + value = fields.imm15(); + break; + case FieldType::ZERO: + value = 0; + break; + default: + assert(false); + } + + // use the value, to possibly add an atom + InstructionAtom atom; + switch (step.decode) { + case DecodeType::GPR: + atom.set_reg(Register(Reg::GPR, value)); + break; + case DecodeType::FPR: + atom.set_reg(Register(Reg::FPR, value)); + break; + case DecodeType::COP0: + atom.set_reg(Register(Reg::COP0, value)); + break; + case DecodeType::PCR: + atom.set_reg(Register(Reg::PCR, value)); + break; + case DecodeType::IMM: + atom.set_imm(value); + break; + case DecodeType::VF: + atom.set_reg(Register(Reg::VF, value)); + break; + case DecodeType::VI: + atom.set_reg(Register(Reg::VI, value)); + break; + case DecodeType::VU_ACC: + atom.set_vu_acc(); + break; + case DecodeType::VU_Q: + atom.set_vu_q(); + break; + case DecodeType::VCALLMS_TARGET: + atom.set_imm(value * 8); + break; + case DecodeType::BRANCH_TARGET: + atom.set_label(file.get_label_id_for(seg_id, (word_id + value + 1) * 4)); + break; + + // NOTE - these change a property of the instruction and don't add an atom. + case DecodeType::IL: + i.il = value; + continue; + case DecodeType::DEST: + i.cop2_dest = value; + continue; + case DecodeType::BC: + i.cop2_bc = value; + continue; + + default: + assert(false); + } + + if (step.is_src) { + i.add_src(atom); + } else { + i.add_dst(atom); + } + } + + if (word.kind == LinkedWord::SYM_OFFSET) { + bool fixed = false; + for (int j = 0; j < i.n_src; j++) { + if (i.src[j].kind == InstructionAtom::IMM) { + fixed = true; + i.src[j].set_sym(word.symbol_name); + } + } + assert(fixed); + } + + if (word.kind == LinkedWord::HI_PTR) { + assert(i.kind == InstructionKind::LUI); + bool fixed = false; + for (int j = 0; j < i.n_src; j++) { + if (i.src[j].kind == InstructionAtom::IMM) { + fixed = true; + i.src[j].set_label(word.label_id); + } + } + assert(fixed); + } + + if (word.kind == LinkedWord::LO_PTR) { + assert(i.kind == InstructionKind::ORI); + bool fixed = false; + for (int j = 0; j < i.n_src; j++) { + if (i.src[j].kind == InstructionAtom::IMM) { + fixed = true; + i.src[j].set_label(word.label_id); + } + } + assert(fixed); + } + + return i; +} \ No newline at end of file diff --git a/decompiler/Disasm/InstructionDecode.h b/decompiler/Disasm/InstructionDecode.h new file mode 100644 index 000000000..7cefc76e1 --- /dev/null +++ b/decompiler/Disasm/InstructionDecode.h @@ -0,0 +1,17 @@ +/*! + * @file InstructionDecode.h + * The Instruction Decoder - converts a LinkedWord into a Instruction. + * This is the part of the disassembler that decodes MIPS instructions. + */ + +#ifndef NEXT_INSTRUCTIONDECODE_H +#define NEXT_INSTRUCTIONDECODE_H + +#include "Instruction.h" + +class LinkedWord; +class LinkedObjectFile; + +Instruction decode_instruction(LinkedWord& word, LinkedObjectFile& file, int seg_id, int word_id); + +#endif // NEXT_INSTRUCTIONDECODE_H diff --git a/decompiler/Disasm/InstructionMatching.cpp b/decompiler/Disasm/InstructionMatching.cpp new file mode 100644 index 000000000..05d5c437a --- /dev/null +++ b/decompiler/Disasm/InstructionMatching.cpp @@ -0,0 +1,350 @@ +/*! + * @file InstructionMatching.cpp + * Utilities for checking if an instruction matches some criteria. + */ + +#include +#include "InstructionMatching.h" + +/*! + * Check if the given instruction stores a GPR with the specified parameters. + */ +bool is_no_link_gpr_store(const Instruction& instr, + MatchParam size, + MatchParam src, + MatchParam offset, + MatchParam dest) { + // match the opcode + if (!size.is_wildcard) { + switch (size.value) { + case 1: + if (instr.kind != InstructionKind::SB) { + return false; + } + break; + case 2: + if (instr.kind != InstructionKind::SH) { + return false; + } + break; + case 4: + if (instr.kind != InstructionKind::SW) { + return false; + } + break; + case 8: + if (instr.kind != InstructionKind::SD) { + return false; + } + break; + case 16: + if (instr.kind != InstructionKind::SQ) { + return false; + } + break; + default: + assert(false); + } + } else { + // just make sure it's a gpr store + if (!is_gpr_store(instr)) { + return false; + } + } + + assert(instr.n_src == 3); + + // match other arguments + return src == instr.src[0].get_reg() && offset == instr.src[1].get_imm() && + dest == instr.src[2].get_reg(); +} + +/*! + * Check if the given instruction loads a GPR with the specified parameters. + * LD and LQ count as signed, unsigned, and "wildcard signed" loads. + * LWL/LWR/LDL/LDR will never match. + * + * "no ll" means no link or label + */ +bool is_no_ll_gpr_load(const Instruction& instr, + MatchParam size, + MatchParam is_signed, + MatchParam dst_reg, + MatchParam offset, + MatchParam mem_reg) { + // match the opcode + if (!size.is_wildcard) { + if (is_signed.is_wildcard) { + switch (size.value) { + case 1: + if (instr.kind != InstructionKind::LB && instr.kind != InstructionKind::LBU) { + return false; + } + break; + case 2: + if (instr.kind != InstructionKind::LH && instr.kind != InstructionKind::LHU) { + return false; + } + break; + case 4: + if (instr.kind != InstructionKind::LW && instr.kind != InstructionKind::LWU) { + return false; + } + break; + case 8: + if (instr.kind != InstructionKind::LD) { + return false; + } + break; + case 16: + if (instr.kind != InstructionKind::LQ) { + return false; + } + break; + default: + assert(false); + } + } else { + if (is_signed.value) { + switch (size.value) { + case 1: + if (instr.kind != InstructionKind::LB) { + return false; + } + break; + case 2: + if (instr.kind != InstructionKind::LH) { + return false; + } + break; + case 4: + if (instr.kind != InstructionKind::LW) { + return false; + } + break; + case 8: + if (instr.kind != InstructionKind::LD) { + return false; + } + break; + case 16: + if (instr.kind != InstructionKind::LQ) { + return false; + } + break; + default: + assert(false); + } + } else { + switch (size.value) { + case 1: + if (instr.kind != InstructionKind::LBU) { + return false; + } + break; + case 2: + if (instr.kind != InstructionKind::LHU) { + return false; + } + break; + case 4: + if (instr.kind != InstructionKind::LWU) { + return false; + } + break; + case 8: + if (instr.kind != InstructionKind::LD) { + return false; + } + break; + case 16: + if (instr.kind != InstructionKind::LQ) { + return false; + } + break; + default: + assert(false); + } + } + } + } else { + // just make sure it's a gpr store + if (!is_gpr_load(instr, is_signed)) { + return false; + } + } + + // match other arguments + return dst_reg == instr.get_dst(0).get_reg() && offset == instr.get_src(0).get_imm() && + mem_reg == instr.get_src(1).get_reg(); +} + +/*! + * Check if the instruction stores an FPR (SWC1) + * "no ll" means that there is no label or linking involved. + */ +bool is_no_ll_fpr_store(const Instruction& instr, + MatchParam src, + MatchParam offset, + MatchParam dest) { + return instr.kind == InstructionKind::SWC1 && src == instr.src[0].get_reg() && + offset == instr.src[1].get_imm() && dest == instr.src[2].get_reg(); +} +/*! + * Check if the instruction loads an FPR (LWC1) + * "no ll" means that there is no label or linking involved. + */ +bool is_no_ll_fpr_load(const Instruction& instr, + MatchParam dst_reg, + MatchParam offset, + MatchParam mem_reg) { + return instr.kind == InstructionKind::LWC1 && dst_reg == instr.get_dst(0).get_reg() && + offset == instr.get_src(0).get_imm() && mem_reg == instr.get_src(1).get_reg(); +} + +namespace { +auto gpr_stores = {InstructionKind::SB, InstructionKind::SH, InstructionKind::SW, + InstructionKind::SD, InstructionKind::SQ}; +auto gpr_signed_loads = {InstructionKind::LB, InstructionKind::LH, InstructionKind::LW, + InstructionKind::LD, InstructionKind::LQ}; +auto gpr_unsigned_loads = {InstructionKind::LBU, InstructionKind::LHU, InstructionKind::LWU, + InstructionKind::LD, InstructionKind::LQ}; +auto gpr_all_loads = {InstructionKind::LBU, InstructionKind::LB, InstructionKind::LH, + InstructionKind::LHU, InstructionKind::LW, InstructionKind::LWU, + InstructionKind::SD, InstructionKind::SQ}; +} // namespace + +/*! + * Is this a GPR store instruction? sb,sh,sw,sd,sq + */ +bool is_gpr_store(const Instruction& instr) { + for (auto x : gpr_stores) { + if (instr.kind == x) { + return true; + } + } + return false; +} + +/*! + * Is this a GPR load instruction? + * Only LB/LBU,LH/LHU,LW/LWU,LD,LQ are treated as loads + * The LD, LQ opcodes are both signed, unsigned, and "wildcard signed" + */ +bool is_gpr_load(const Instruction& instr, MatchParam is_signed) { + if (is_signed.is_wildcard) { + for (auto x : gpr_all_loads) { + if (instr.kind == x) { + return true; + } + } + return false; + } else if (is_signed.value) { + for (auto x : gpr_signed_loads) { + if (instr.kind == x) { + return true; + } + } + return false; + } else { + for (auto x : gpr_unsigned_loads) { + if (instr.kind == x) { + return true; + } + } + return false; + } +} + +/*! + * Given a store, get the offset as an integer. + */ +int32_t get_gpr_store_offset_as_int(const Instruction& instr) { + assert(is_gpr_store(instr)); + assert(instr.n_src == 3); + return instr.src[1].get_imm(); +} + +/*! + * Match an instruction in the form OP, dst, src0, src1 where all args are registers. + */ +bool is_gpr_3(const Instruction& instr, + MatchParam kind, + MatchParam dst, + MatchParam src0, + MatchParam src1) { + return kind == instr.kind && dst == instr.get_dst(0).get_reg() && + src0 == instr.get_src(0).get_reg() && src1 == instr.get_src(1).get_reg(); +} + +/*! + * Match an instruction in the form OP, dst, src0, src1 where all args are registers, except for + * src1, which is an integer. + */ +bool is_gpr_2_imm_int(const Instruction& instr, + MatchParam kind, + MatchParam dst, + MatchParam src, + MatchParam imm) { + return kind == instr.kind && dst == instr.get_dst(0).get_reg() && + src == instr.get_src(0).get_reg() && imm == instr.get_src(1).get_imm(); +} + +/*! + * Create a Register for a GPR. + */ +Register make_gpr(Reg::Gpr gpr) { + return Register(Reg::GPR, gpr); +} + +/*! + * Create a Register for an FPR. + */ +Register make_fpr(int fpr) { + return Register(Reg::FPR, fpr); +} + +/*! + * Is this a "nop"? More specifically, it checks for sll r0, r0, 0, the recommended MIPS nop. + */ +bool is_nop(const Instruction& instr) { + return is_gpr_2_imm_int(instr, InstructionKind::SLL, make_gpr(Reg::R0), make_gpr(Reg::R0), 0); +} + +/*! + * Is this jr ra? + */ +bool is_jr_ra(const Instruction& instr) { + return instr.kind == InstructionKind::JR && instr.get_src(0).get_reg() == make_gpr(Reg::RA); +} + +bool is_branch(const Instruction& instr, MatchParam likely) { + const auto& info = instr.get_info(); + if (likely.is_wildcard) { + return info.is_branch || info.is_branch_likely; + } else if (likely.value) { + return info.is_branch_likely; + } else { + return info.is_branch && !info.is_branch_likely; + } +} + +bool is_always_branch(const Instruction& instr) { + if (!is_branch(instr, {})) { + return false; + } + + auto r0 = make_gpr(Reg::R0); + if (instr.kind == InstructionKind::BEQ && instr.get_src(0).get_reg() == r0 && + instr.get_src(1).get_reg() == r0) { + return true; + } + + if (instr.kind == InstructionKind::BEQL && instr.get_src(0).get_reg() == r0 && + instr.get_src(1).get_reg() == r0) { + assert(false); + return true; + } + + return false; +} \ No newline at end of file diff --git a/decompiler/Disasm/InstructionMatching.h b/decompiler/Disasm/InstructionMatching.h new file mode 100644 index 000000000..96098cac9 --- /dev/null +++ b/decompiler/Disasm/InstructionMatching.h @@ -0,0 +1,69 @@ +#ifndef JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H +#define JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H + +#include "Instruction.h" + +template +struct MatchParam { + MatchParam() { is_wildcard = true; } + + // intentionally not explicit so you don't have to put MatchParam(blah) everywhere + MatchParam(T x) { + value = x; + is_wildcard = false; + } + + T value; + bool is_wildcard = true; + + bool operator==(const T& other) { return is_wildcard || (value == other); } + bool operator!=(const T& other) { return !(*this == other); } +}; + +bool is_no_link_gpr_store(const Instruction& instr, + MatchParam size, + MatchParam src, + MatchParam offset, + MatchParam dest); +bool is_no_ll_gpr_load(const Instruction& instr, + MatchParam size, + MatchParam is_signed, + MatchParam dst_reg, + MatchParam offset, + MatchParam mem_reg); + +bool is_no_ll_fpr_store(const Instruction& instr, + MatchParam src, + MatchParam offset, + MatchParam dest); +bool is_no_ll_fpr_load(const Instruction& instr, + MatchParam dst_reg, + MatchParam offset, + MatchParam mem_reg); + +bool is_gpr_store(const Instruction& instr); +bool is_gpr_load(const Instruction& instr, MatchParam is_signed); +int32_t get_gpr_store_offset_as_int(const Instruction& instr); + +bool is_gpr_3(const Instruction& instr, + MatchParam kind, + MatchParam dst, + MatchParam src0, + MatchParam src1); + +bool is_gpr_2_imm_int(const Instruction& instr, + MatchParam kind, + MatchParam dst, + MatchParam src, + MatchParam imm); + +bool is_nop(const Instruction& instr); +bool is_jr_ra(const Instruction& instr); + +Register make_gpr(Reg::Gpr gpr); +Register make_fpr(int fpr); + +bool is_branch(const Instruction& instr, MatchParam likely); +bool is_always_branch(const Instruction& instr); + +#endif // JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H diff --git a/decompiler/Disasm/OpcodeInfo.cpp b/decompiler/Disasm/OpcodeInfo.cpp new file mode 100644 index 000000000..102493bb2 --- /dev/null +++ b/decompiler/Disasm/OpcodeInfo.cpp @@ -0,0 +1,499 @@ +#include "OpcodeInfo.h" +#include + +OpcodeInfo gOpcodeInfo[(uint32_t)InstructionKind::EE_OP_MAX]; + +typedef InstructionKind IK; +typedef FieldType FT; +typedef DecodeType DT; + +static OpcodeInfo& def(IK k, const char* name) { + gOpcodeInfo[(uint32_t)k].defined = true; + gOpcodeInfo[(uint32_t)k].name = name; + return gOpcodeInfo[(uint32_t)k]; +} + +static OpcodeInfo& def_branch(IK k, const char* name) { + auto& result = def(k, name); + result.is_branch = true; + result.has_delay_slot = true; + return result; +} + +static OpcodeInfo& def_branch_likely(IK k, const char* name) { + auto& result = def(k, name); + result.is_branch = true; + result.is_branch_likely = true; + result.has_delay_slot = true; + return result; +} + +static OpcodeInfo& def_store(IK k, const char* name) { + auto& result = def(k, name); + result.is_store = true; + return result; +} + +static OpcodeInfo& def_load(IK k, const char* name) { + auto& result = def(k, name); + result.is_load = true; + return result; +} + +static OpcodeInfo& drt_srs_ssimm16(OpcodeInfo& info) { + return info.dst_gpr(FT::RT).src_gpr(FT::RS).src(FT::SIMM16, DT::IMM); +} + +static OpcodeInfo& srt_ssimm16_srs(OpcodeInfo& info) { + return info.src_gpr(FT::RT).src(FT::SIMM16, DT::IMM).src_gpr(FT::RS); +} + +static OpcodeInfo& drt_ssimm16_srs(OpcodeInfo& info) { + return info.dst_gpr(FT::RT).src(FT::SIMM16, DT::IMM).src_gpr(FT::RS); +} + +static OpcodeInfo& drd_srs_srt(OpcodeInfo& info) { + return info.dst_gpr(FT::RD).src_gpr(FT::RS).src_gpr(FT::RT); +} + +static OpcodeInfo& drd_srt_srs(OpcodeInfo& info) { + return info.dst_gpr(FT::RD).src_gpr(FT::RT).src_gpr(FT::RS); +} + +static OpcodeInfo& drd_srt_ssa(OpcodeInfo& info) { + return info.dst_gpr(FT::RD).src_gpr(FT::RT).src(FT::SA, DT::IMM); +} + +static OpcodeInfo& srs_srt_bt(OpcodeInfo& info) { + return info.src_gpr(FT::RS).src_gpr(FT::RT).src(FT::SIMM16, DT::BRANCH_TARGET); +} + +static OpcodeInfo& srs_bt(OpcodeInfo& info) { + return info.src_gpr(FT::RS).src(FT::SIMM16, DT::BRANCH_TARGET); +} + +static OpcodeInfo& bt(OpcodeInfo& info) { + return info.src(FT::SIMM16, DT::BRANCH_TARGET); +} + +static OpcodeInfo& dfd_sfs_sft(OpcodeInfo& info) { + return info.dst_fpr(FT::FD).src_fpr(FT::FS).src_fpr(FT::FT); +} + +static OpcodeInfo& sfs_sft(OpcodeInfo& info) { + return info.src_fpr(FT::FS).src_fpr(FT::FT); +} + +static OpcodeInfo& dfd_sfs(OpcodeInfo& info) { + return info.dst_fpr(FT::FD).src_fpr(FT::FS); +} + +static OpcodeInfo& drd(OpcodeInfo& info) { + return info.dst_gpr(FT::RD); +} + +static OpcodeInfo& cd_dvft_svfs(OpcodeInfo& info) { + return info.src(FT::DEST, DT::DEST).dst_vf(FT::FT).src_vf(FT::FS); +} + +static OpcodeInfo& cd_dvfd_svfs_svft(OpcodeInfo& info) { + return info.src(FT::DEST, DT::DEST).dst_vf(FT::FD).src_vf(FT::FS).src_vf(FT::FT); +} + +static OpcodeInfo& cb_cd_dvfd_svfs_svft(OpcodeInfo& info) { + return info.src(FT::BC, DT::BC) + .src(FT::DEST, DT::DEST) + .dst_vf(FT::FD) + .src_vf(FT::FS) + .src_vf(FT::FT); +} + +static OpcodeInfo& cb_cd_dacc_svfs_svft(OpcodeInfo& info) { + return info.src(FT::BC, DT::BC) + .src(FT::DEST, DT::DEST) + .dst(FT::ZERO, DT::VU_ACC) + .src_vf(FT::FS) + .src_vf(FT::FT); +} + +static OpcodeInfo& cd_dvfd_svfs_sq(OpcodeInfo& info) { + return info.src(FT::DEST, DT::DEST).dst_vf(FT::FD).src_vf(FT::FS).src(FT::ZERO, DT::VU_Q); +} + +static OpcodeInfo& cd_dacc_svfs_svft(OpcodeInfo& info) { + return info.src(FT::DEST, DT::DEST).dst(FT::ZERO, DT::VU_ACC).src_vf(FT::FS).src_vf(FT::FT); +} + +void init_opcode_info() { + gOpcodeInfo[0].name = ";; ??????"; + + // RT, RS, SIMM + drt_srs_ssimm16(def(IK::DADDIU, "daddiu")); // Doubleword Add Immediate Unsigned + drt_srs_ssimm16(def(IK::ADDIU, "addiu")); // Add Immediate Unsigned Word + drt_srs_ssimm16(def(IK::SLTI, "slti")); // Set on Less Than Immediate + drt_srs_ssimm16(def(IK::SLTIU, "sltiu")); // Set on Less Than Immediate Unsigned + + // stores in srt_ssimm16_srs + srt_ssimm16_srs(def_store(IK::SB, "sb")); // Store Byte + srt_ssimm16_srs(def_store(IK::SH, "sh")); // Store Halfword + srt_ssimm16_srs(def_store(IK::SW, "sw")); // Store Word + srt_ssimm16_srs(def_store(IK::SD, "sd")); // Store Doubleword + srt_ssimm16_srs(def_store(IK::SQ, "sq")); // Store Quadword + + // loads in dsrt_ssimm16_srs + drt_ssimm16_srs(def_load(IK::LB, "lb")); // Load Byte + drt_ssimm16_srs(def_load(IK::LBU, "lbu")); // Load Byte Unsigned + drt_ssimm16_srs(def_load(IK::LH, "lh")); // Load Halfword + drt_ssimm16_srs(def_load(IK::LHU, "lhu")); // Load Halfword Unsigned + drt_ssimm16_srs(def_load(IK::LW, "lw")); // Load Word + drt_ssimm16_srs(def_load(IK::LWU, "lwu")); // Load Word Unsigned + drt_ssimm16_srs(def_load(IK::LD, "ld")); // Load Doubleword + drt_ssimm16_srs(def_load(IK::LQ, "lq")); // Load Quadword + drt_ssimm16_srs(def_load(IK::LDR, "ldr")); // Load Doubleword Left + drt_ssimm16_srs(def_load(IK::LDL, "ldl")); // Load Doubleword Right + drt_ssimm16_srs(def_load(IK::LWL, "lwl")); // Load Word Left + drt_ssimm16_srs(def_load(IK::LWR, "lwr")); // Load Word Right + + // drd_srs_srt + drd_srs_srt(def(IK::DADDU, "daddu")); // Doubleword Add Unsigned + drd_srs_srt(def(IK::SUBU, "subu")); // Subtract Unsigned Word + drd_srs_srt(def(IK::ADDU, "addu")); // Add Unsigned Word + drd_srs_srt(def(IK::DSUBU, "dsubu")); // Doubleword Subtract Unsigned + drd_srs_srt(def(IK::MULT3, "mult3")); // Multiply Word + drd_srs_srt(def(IK::MULTU3, "multu3")); // Multiply Unsigned Word + drd_srs_srt(def(IK::AND, "and")); // And + drd_srs_srt(def(IK::OR, "or")); // Or + drd_srs_srt(def(IK::NOR, "nor")); // Not Or + drd_srs_srt(def(IK::XOR, "xor")); // Exclusive Or + drd_srs_srt(def(IK::MOVN, "movn")); // Move Conditional on Not Zero + drd_srs_srt(def(IK::MOVZ, "movz")); // Move Conditional on Zero + drd_srs_srt(def(IK::SLT, "slt")); // Set on Less Than + drd_srs_srt(def(IK::SLTU, "sltu")); // Set on Less Than Unsigned + + // fixed shifts + drd_srt_ssa(def(IK::SLL, "sll")); // Shift Left Logical + drd_srt_ssa(def(IK::SRA, "sra")); // Shift Right Arithmetic + drd_srt_ssa(def(IK::SRL, "srl")); // Shift Right Logical + drd_srt_ssa(def(IK::DSLL, "dsll")); // Doubleword Shift Left Logical + drd_srt_ssa(def(IK::DSLL32, "dsll32")); // Doubleword Shift Left Logical Plus 32 + drd_srt_ssa(def(IK::DSRA, "dsra")); // Doubleword Shift Right Arithmetic + drd_srt_ssa(def(IK::DSRA32, "dsra32")); // Doubleword Shift Right Arithmetic Plus 32 + drd_srt_ssa(def(IK::DSRL, "dsrl")); // Doubleword Shift Right Logical + drd_srt_ssa(def(IK::DSRL32, "dsrl32")); // Doubleword Shift Right Logical Plus 32 + + // variable shifts + drd_srt_srs(def(IK::DSRAV, "dsrav")); // Doubleword Shift Right Arithmetic Variable + drd_srt_srs(def(IK::SLLV, "sllv")); // Shift Word Left Logical Variable + drd_srt_srs(def(IK::DSLLV, "dsllv")); // Doubleword Shift Left Logical Variable + drd_srt_srs(def(IK::DSRLV, "dsrlv")); // Doubleword Shift Right Logical Variable + + // branch (two registers) + srs_srt_bt(def_branch(IK::BEQ, "beq")); // Branch on Equal + srs_srt_bt(def_branch(IK::BNE, "bne")); // Branch on Not Equal + srs_srt_bt(def_branch_likely(IK::BEQL, "beql")); // Branch on Equal Likely + srs_srt_bt(def_branch_likely(IK::BNEL, "bnel")); // Branch on Not Equal Likely + + // branch (one register) + srs_bt(def_branch(IK::BLTZ, "bltz")); // Branch on Less Than Zero + srs_bt(def_branch(IK::BGEZ, "bgez")); // Branch on Greater Than or Equal to Zero + srs_bt(def_branch(IK::BLEZ, "blez")); // Branch on Less Than or Equal to Zero + srs_bt(def_branch(IK::BGTZ, "bgtz")); // Branch on Greater Than Zero + srs_bt(def_branch(IK::BGEZAL, "bgezal")); // Branch on Greater Than or Equal to Zero and Link + srs_bt(def_branch_likely(IK::BLTZL, "bltzl")); // Branch on Less Than Zero Likely + srs_bt(def_branch_likely(IK::BGTZL, "bgtzl")); // Branch on Greater Than Zero Likely + srs_bt(def_branch_likely(IK::BGEZL, "bgezl")); // Branch on Greater Than or Equal to Zero Likely + + // weird ones + def(IK::DIV, "div").src_gpr(FT::RS).src_gpr(FT::RT); // Divide Word + def(IK::DIVU, "divu").src_gpr(FT::RS).src_gpr(FT::RT); // Divide Unsigned Word + + def(IK::ORI, "ori").dst_gpr(FT::RT).src_gpr(FT::RS).src(FT::ZIMM16, DT::IMM); // Or Immediate + def(IK::XORI, "xori") + .dst_gpr(FT::RT) + .src_gpr(FT::RS) + .src(FT::ZIMM16, DT::IMM); // Exclusive Or Immediate + def(IK::ANDI, "andi").dst_gpr(FT::RT).src_gpr(FT::RS).src(FT::ZIMM16, DT::IMM); // And Immediate + + def(IK::LUI, "lui").dst_gpr(FT::RT).src(FT::SIMM16, DT::IMM); // Load Upper Immediate + def(IK::JALR, "jalr").dst_gpr(FT::RD).src_gpr(FT::RS).has_delay_slot = + true; // Jump and Link Register + def(IK::JR, "jr").src_gpr(FT::RS).has_delay_slot = true; // Jump Register + + def_load(IK::LWC1, "lwc1") + .dst_fpr(FT::FT) + .src(FT::SIMM16, DT::IMM) + .src_gpr(FT::RS); // Load Word to Floating Point + def_store(IK::SWC1, "swc1") + .src_fpr(FT::FT) + .src(FT::SIMM16, DT::IMM) + .src_gpr(FT::RS); // Store Word from Floating Point + + // weird moves + def(IK::MFC1, "mfc1").dst_gpr(FT::RT).src_fpr(FT::FS); // Move Word from Floating Point + def(IK::MTC1, "mtc1").src_gpr(FT::RT).dst_fpr(FT::FS); // Move Word to Floating Point + def(IK::MTC0, "mtc0") + .src_gpr(FT::RT) + .dst(FT::RD, DT::COP0); // Move to System Control Coprocessor + def(IK::MFC0, "mfc0") + .dst_gpr(FT::RT) + .src(FT::RD, DT::COP0); // Move from System Control Coprocessor + def(IK::MTDAB, "mtdab").src_gpr(FT::RT); // Move to Data Address Breakpoint Register + def(IK::MTDABM, "mtdabm").src_gpr(FT::RT); // Move to Data Address Breakpoint Mask Register + drd(def(IK::MFHI, "mfhi")); // Move from HI Register + drd(def(IK::MFLO, "mflo")); // Move from LO Register + def(IK::MTLO1, "mtlo1").src_gpr(FT::RS); // Move to LO1 Register + drd(def(IK::MFLO1, "mflo1")); // Move from LO1 Register + drd(def(IK::PMFHL_UW, "pmfhl.uw")); // Parallel Move From HI/LO Register + drd(def(IK::PMFHL_LW, "pmfhl.lw")); + drd(def(IK::PMFHL_LH, "pmfhl.lh")); + def(IK::MFPC, "mfpc").dst_gpr(FT::RT).src(FT::PCR, DT::PCR); // Move from Performance Counter + def(IK::MTPC, "mtpc").src_gpr(FT::RT).dst(FT::PCR, DT::PCR); // Move to Performance Counter + + // other weirds + def(IK::SYSCALL, "syscall").src(FT::SYSCALL, DT::IMM); // System Call + def(IK::CACHE_DXWBIN, "cache dxwbin") + .src_gpr(FT::RS) + .src(FT::SIMM16, DT::IMM); // Cache Operation (Index Writeback Invalidate) + def(IK::PREF, "pref").src_gpr(FT::RT).src(FT::SIMM16, DT::IMM).src_gpr(FT::RS); // Prefetch + + // plains + def(IK::SYNCP, "sync.p"); // Synchronize Shared Memory (Pipeline) + def(IK::SYNCL, "sync.l"); // Synchronize Shared Memory (Load) + def(IK::ERET, "eret"); // Exception Return + def(IK::EI, "ei"); // Enable Interrupt + + drd_srs_srt(def(IK::PPACB, "ppacb")); // Parallel Pack to Byte + drd_srs_srt(def(IK::PPACH, "ppach")); // Parallel Pack to Halfword + drd_srs_srt(def(IK::PPACW, "ppacw")); // Parallel Pack to Word + drd_srs_srt(def(IK::PADDH, "paddh")); // Parallel Add Halfword + drd_srs_srt(def(IK::PADDW, "paddw")); // Parallel Add Word + drd_srs_srt(def(IK::PSUBW, "psubw")); // Parallel Subtract Word + drd_srs_srt(def(IK::PMINH, "pminh")); // Parallel Minimize Halfword + drd_srs_srt(def(IK::PMINW, "pminw")); // Parallel Minimize Word + drd_srs_srt(def(IK::PMAXH, "pmaxh")); // Parallel Maximize Halfword + drd_srs_srt(def(IK::PMAXW, "pmaxw")); // Parallel Maximize Word + drd_srs_srt(def(IK::PEXTLB, "pextlb")); // Parallel Extend Lower from Byte + drd_srs_srt(def(IK::PEXTLH, "pextlh")); // Parallel Extend Lower from Halfword + drd_srs_srt(def(IK::PEXTLW, "pextlw")); // Parallel Extend Lower from Word + drd_srs_srt(def(IK::PCGTW, "pcgtw")); // Parallel Compare for Greater Than Word + drd_srs_srt(def(IK::PCEQB, "pceqb")); // Parallel Compare for Equal Byte + drd_srs_srt(def(IK::PCEQW, "pceqw")); // Parallel Compare for Equal Word + drd_srs_srt(def(IK::PEXTUB, "pextub")); // Parallel Extend Upper from Byte + drd_srs_srt(def(IK::PEXTUH, "pextuh")); // Parallel Extend Upper from Halfword + drd_srs_srt(def(IK::PEXTUW, "pextuw")); // Parallel Extend Upper from Word + drd_srs_srt(def(IK::PCPYUD, "pcpyud")); // Parallel Copy Upper Doubleword + drd_srs_srt(def(IK::PCPYLD, "pcpyld")); // Parallel Copy Lower Doubleword + drd_srs_srt(def(IK::PMADDH, "pmaddh")); // Parallel Multiply-Add Halfword + drd_srs_srt(def(IK::PMULTH, "pmulth")); // Parallel Multiply Halfword + drd_srs_srt(def(IK::PEXEW, "pexew")); // Parallel Exchange Even Word + drd_srs_srt(def(IK::PINTEH, "pinteh")); // Parallel Interleave Even Halfword + drd_srs_srt(def(IK::PAND, "pand")); // Parallel And + drd_srs_srt(def(IK::POR, "por")); // Parallel Or + drd_srs_srt(def(IK::PNOR, "pnor")); // Parallel Not Or + + drd_srt_ssa(def(IK::PSLLW, "psllw")); // Parallel Shift Left Logical Word + drd_srt_ssa(def(IK::PSLLH, "psllh")); // Parallel Shift Left Logical Halfword + drd_srt_ssa(def(IK::PSRAW, "psraw")); // Parallel Shift Right Arithmetic Word + drd_srt_ssa(def(IK::PSRAH, "psrah")); // Parallel Shift Right Arithmetic Halfword + drd_srt_ssa(def(IK::PSRLH, "psrlh")); // Parallel Shift Right Logical Halfword + + def(IK::PLZCW, "plzcw").dst_gpr(FT::RD).src_gpr(FT::RS); // Parallel Leading Zero Count Word + def(IK::PABSW, "pabsw").dst_gpr(FT::RD).src_gpr(FT::RT); // Parallel Absolute Word + def(IK::PROT3W, "prot3w").dst_gpr(FT::RD).src_gpr(FT::RT); // Parallel Rotate 3 Word + def(IK::PCPYH, "pcpyh").dst_gpr(FT::RD).src_gpr(FT::RT); // Parallel Copy Halfword + + // COP1 + + // branch (no registers) + bt(def_branch(IK::BC1F, "bc1f")); // Branch on FP False + bt(def_branch(IK::BC1T, "bc1t")); // Branch on FP True + bt(def_branch_likely(IK::BC1FL, "bc1fl")); // Branch on FP False Likely + bt(def_branch_likely(IK::BC1TL, "bc1tl")); // Branch on FP True Likely + + dfd_sfs_sft(def(IK::ADDS, "add.s")); // Floating Point Add + dfd_sfs_sft(def(IK::SUBS, "sub.s")); // Floating Point Subtract + dfd_sfs_sft(def(IK::MULS, "mul.s")); // Floating Point Multiply + dfd_sfs_sft(def(IK::DIVS, "div.s")); // Floating Point Divide + dfd_sfs_sft(def(IK::MINS, "min.s")); // Floating Point Minimum + dfd_sfs_sft(def(IK::MAXS, "max.s")); // Floating Point Maximum + dfd_sfs_sft(def(IK::MADDS, "madd.s")); // Floating Point Multiply-Add + dfd_sfs_sft(def(IK::MSUBS, "msub.s")); // Floating Point Multiply and Subtract + dfd_sfs_sft(def(IK::RSQRTS, "rsqrt.s")); // Floating Point Reciporcal Square Root + + dfd_sfs(def(IK::ABSS, "abs.s")); // Floating Point Absolute Value + dfd_sfs(def(IK::NEGS, "neg.s")); // Floating Point Negate + dfd_sfs(def(IK::CVTSW, "cvt.s.w")); // Fixed-point Convert to Single Floating Point + dfd_sfs(def(IK::CVTWS, "cvt.w.s")); // Floating Point Convert to Word Fixed-point + dfd_sfs(def(IK::MOVS, "mov.s")); // Floating Point Move + dfd_sfs(def(IK::SQRTS, "sqrt.s")); // Floating Point Square Root + + sfs_sft(def(IK::CLTS, "c.lt.s")); // Floating Point Compare + sfs_sft(def(IK::CLES, "c.le.s")); // Floating Point Compare + sfs_sft(def(IK::CEQS, "c.eq.s")); // Floating Point Compare + sfs_sft(def(IK::MULAS, "mula.s")); // Floating Point Multiply to Accumulator + sfs_sft(def(IK::MADDAS, "madda.s")); // Floating Point Multiply-Add to Accumulator + sfs_sft(def(IK::ADDAS, "adda.s")); // Floating Point Add to Accumulator + sfs_sft(def(IK::MSUBAS, "msuba.s")); // Floating Point Multiply and Subtract from Accumulator + + // COP2 weirds + def_store(IK::SQC2, "sqc2") + .src(FT::FT, DT::VF) + .src(FT::SIMM16, DT::IMM) + .src_gpr(FT::RS); // Store Quadword from COP2 + def_load(IK::LQC2, "lqc2") + .dst(FT::FT, DT::VF) + .src(FT::SIMM16, DT::IMM) + .src_gpr(FT::RS); // Load Quadword to COP2 + + // COP2 + cd_dvft_svfs(def(IK::VMOVE, "vmove")); // Transfer between Floating-Point Registers + cd_dvft_svfs(def(IK::VFTOI0, "vftoi0")); // Conversion to Fixed Point + cd_dvft_svfs(def(IK::VFTOI4, "vftoi4")); // Conversion to Fixed Point + cd_dvft_svfs(def(IK::VFTOI12, "vftoi12")); // Conversion to Fixed Point + cd_dvft_svfs(def(IK::VITOF0, "vitof0")); // Conversion to Floating Point Number + cd_dvft_svfs(def(IK::VITOF12, "vitof12")); // Conversion to Floating Point Number + cd_dvft_svfs(def(IK::VITOF15, "vitof15")); // Conversion to Floating Point Number + cd_dvft_svfs(def(IK::VABS, "vabs")); // Absolute Value + + cd_dvfd_svfs_svft(def(IK::VADD, "vadd")); + cd_dvfd_svfs_svft(def(IK::VSUB, "vsub")); + cd_dvfd_svfs_svft(def(IK::VMUL, "vmul")); + cd_dvfd_svfs_svft(def(IK::VMINI, "vmini")); + cd_dvfd_svfs_svft(def(IK::VMAX, "vmax")); + cd_dvfd_svfs_svft(def(IK::VOPMSUB, "vopmsub")); + cd_dvfd_svfs_svft(def(IK::VMADD, "vmadd")); + cd_dvfd_svfs_svft(def(IK::VMSUB, "vmsub")); + + cb_cd_dvfd_svfs_svft(def(IK::VSUB_BC, "vsub")); + cb_cd_dvfd_svfs_svft(def(IK::VADD_BC, "vadd")); + cb_cd_dvfd_svfs_svft(def(IK::VMADD_BC, "vmadd")); + cb_cd_dvfd_svfs_svft(def(IK::VMSUB_BC, "vmsub")); + cb_cd_dvfd_svfs_svft(def(IK::VMUL_BC, "vmul")); + cb_cd_dvfd_svfs_svft(def(IK::VMINI_BC, "vmini")); + cb_cd_dvfd_svfs_svft(def(IK::VMAX_BC, "vmax")); + + cb_cd_dacc_svfs_svft(def(IK::VADDA_BC, "vadda")); + cb_cd_dacc_svfs_svft(def(IK::VMADDA_BC, "vmadda")); + cb_cd_dacc_svfs_svft(def(IK::VMULA_BC, "vmula")); + cb_cd_dacc_svfs_svft(def(IK::VMSUBA_BC, "vmsuba")); + + cd_dvfd_svfs_sq(def(IK::VADDQ, "vaddq")); + cd_dvfd_svfs_sq(def(IK::VSUBQ, "vsubq")); + cd_dvfd_svfs_sq(def(IK::VMULQ, "vmulq")); + cd_dvfd_svfs_sq(def(IK::VMSUBQ, "vmsubq")); + + cd_dacc_svfs_svft(def(IK::VMULA, "vmula")); + cd_dacc_svfs_svft(def(IK::VADDA, "vadda")); + cd_dacc_svfs_svft(def(IK::VMADDA, "vmadda")); + + cd_dacc_svfs_svft(def(IK::VOPMULA, "vopmula")); + + // weird + def(IK::VDIV, "vdiv") + .dst(FT::ZERO, DT::VU_Q) + .src_vf(FT::FS) + .src_vf(FT::FT) + .src(FT::BC, DT::BC); // todo + def(IK::VRSQRT, "vrsqrt") + .dst(FT::ZERO, DT::VU_Q) + .src_vf(FT::FS) + .src_vf(FT::FT) + .src(FT::BC, DT::BC); // todo + def(IK::VCLIP, "vclip").src(FT::DEST, DT::DEST).src_vf(FT::FS).src_vf(FT::FT); + def(IK::VMULAQ, "vmulaq") + .src(FT::DEST, DT::DEST) + .dst(FT::ZERO, DT::VU_ACC) + .src_vf(FT::FS) + .src(FT::ZERO, DT::VU_Q); + + def(IK::VRGET, "vrget").src(FT::DEST, DT::DEST).dst_vf(FT::FT); + + // integer + def(IK::VMTIR, "vmtir").dst(FT::RT, DT::VI).src_vf(FT::FS).src(FT::BC, DT::BC); + def(IK::VIAND, "viand").dst_vi(FT::FD).src_vi(FT::FS).src_vi(FT::FT); + def(IK::VLQI, "vlqi").src(FT::DEST, DT::DEST).dst_vf(FT::FT).src_vi(FT::FS); // todo inc + def(IK::VSQI, "vsqi").src(FT::DEST, DT::DEST).src_vf(FT::FS).src_vi(FT::FT); // todo inc + def(IK::VIADDI, "viaddi").dst_vi(FT::FT).src_vi(FT::FS).src(FT::IMM5, DT::IMM); + + def(IK::QMFC2, "qmfc2").src(FT::IL, DT::IL).dst_gpr(FT::RT).src_vf(FT::FS); + def(IK::QMTC2, "qmtc2").src(FT::IL, DT::IL).src_gpr(FT::RT).dst_vf(FT::FS); + def(IK::VSQRT, "vsqrt").src(FT::BC, DT::BC).dst(FT::ZERO, DT::VU_Q).src_vf(FT::FT); + def(IK::VRXOR, "vrxor").src(FT::BC, DT::BC).src_vf(FT::FS); + def(IK::VRNEXT, "vrnext").src(FT::DEST, DT::DEST).dst_vf(FT::FT); + def(IK::CTC2, "ctc2").src(FT::IL, DT::IL).src_gpr(FT::RT).dst(FT::RD, DT::VI); + def(IK::CFC2, "cfc2").src(FT::IL, DT::IL).dst_gpr(FT::RT).src(FT::RD, DT::VI); + + def(IK::VCALLMS, "vcallms").src(FT::IMM15, DT::VCALLMS_TARGET); + + def(IK::VNOP, "vnop"); + def(IK::VWAITQ, "vwaitq"); + + uint32_t valid_count = 0, total_count = 0; + for (auto& info : gOpcodeInfo) { + if (info.defined) { + valid_count++; + } + total_count++; + } + + // for the UNKNOWN op which shouldn't be valid. + total_count--; + assert(total_count == valid_count); +} + +void OpcodeInfo::step(DecodeStep& s) { + assert(step_count < MAX_DECODE_STEPS); + steps[step_count] = s; + step_count++; + defined = true; +} + +OpcodeInfo& OpcodeInfo::src(FieldType field, DecodeType decode) { + DecodeStep new_step; + new_step.is_src = true; + new_step.field = field; + new_step.decode = decode; + step(new_step); + return *this; +} + +OpcodeInfo& OpcodeInfo::src_gpr(FieldType field) { + return src(field, DT::GPR); +} + +OpcodeInfo& OpcodeInfo::src_fpr(FieldType field) { + return src(field, DT::FPR); +} + +OpcodeInfo& OpcodeInfo::src_vf(FieldType field) { + return src(field, DT::VF); +} + +OpcodeInfo& OpcodeInfo::src_vi(FieldType field) { + return src(field, DT::VI); +} + +OpcodeInfo& OpcodeInfo::dst(FieldType field, DecodeType decode) { + DecodeStep new_step; + new_step.is_src = false; + new_step.field = field; + new_step.decode = decode; + step(new_step); + return *this; +} + +OpcodeInfo& OpcodeInfo::dst_gpr(FieldType field) { + return dst(field, DT::GPR); +} + +OpcodeInfo& OpcodeInfo::dst_fpr(FieldType field) { + return dst(field, DT::FPR); +} + +OpcodeInfo& OpcodeInfo::dst_vf(FieldType field) { + return dst(field, DT::VF); +} + +OpcodeInfo& OpcodeInfo::dst_vi(FieldType field) { + return dst(field, DT::VI); +} \ No newline at end of file diff --git a/decompiler/Disasm/OpcodeInfo.h b/decompiler/Disasm/OpcodeInfo.h new file mode 100644 index 000000000..fd035a140 --- /dev/null +++ b/decompiler/Disasm/OpcodeInfo.h @@ -0,0 +1,351 @@ +/*! + * @file OpcodeInfo.h + * Decoding info for each opcode. + */ + +#ifndef NEXT_OPCODEINFO_H +#define NEXT_OPCODEINFO_H + +#include + +enum class InstructionKind { + UNKNOWN, + + // Integer Math + ADDU, // Add Unsigned Word + ADDIU, // Add Immediate Unsigned Word + DADDU, + DADDIU, // Doubleword Add Immediate Unsigned + SUBU, + DSUBU, + MULT3, // special EE three-operand multiply + MULTU3, + DIV, + DIVU, + + // Stores + SB, + SH, + SW, + SWC1, + SD, + SQ, + SQC2, + + // Loads + LB, + LBU, + LH, + LHU, + LW, + LWU, + LWL, + LWR, + LWC1, + LD, + LDL, + LDR, + LQ, + LQC2, + LUI, + + // Logical + AND, + ANDI, + OR, + ORI, + XOR, + XORI, + NOR, + + // Moves + MOVN, + MOVZ, + MFHI, + MFLO, + MFLO1, + MTLO1, + MFPC, + MTPC, + MTC0, + MFC0, + MTDAB, + MTDABM, + MFC1, + MTC1, + QMFC2, + QMTC2, + CTC2, + CFC2, + + // Jumps + JALR, + JR, + + // Branch + BEQ, + BEQL, + BNE, + BNEL, + BLTZ, + BLTZL, + BGTZ, + BGTZL, + BGEZ, + BGEZL, + BLEZ, + BGEZAL, + + // Shift + SLL, + SLLV, + SRL, + SRA, + DSLL, + DSLL32, + DSLLV, + DSRL, + DSRL32, + DSRLV, + DSRA, + DSRA32, + DSRAV, + + // Compare + SLT, + SLTI, + SLTU, + SLTIU, + + // Weird + SYSCALL, + SYNCP, + SYNCL, + ERET, + EI, + CACHE_DXWBIN, + PREF, + + // MMI unsorted + PSLLW, + PSRAW, + PSRAH, + PLZCW, + PMFHL_UW, + PMFHL_LW, + PMFHL_LH, + PSLLH, + PSRLH, + + // MMI 0 + PEXTLW, + PPACH, + PSUBW, + PCGTW, + PEXTLH, + PEXTLB, + PMAXH, + PPACB, + PADDW, + PADDH, + PMAXW, + PPACW, + + // MMI 1 + PCEQW, + PEXTUW, + PMINH, + PEXTUH, + PEXTUB, + PCEQB, + PMINW, + PABSW, + + // MMI 2 + PCPYLD, + PROT3W, + PAND, + PMADDH, + PMULTH, + PEXEW, + + // MMI 3 + POR, + PCPYUD, + PNOR, + PCPYH, + PINTEH, + + // COP1 / FPU + ADDS, + SUBS, + MULS, + DIVS, + MINS, + MAXS, + ABSS, + NEGS, + CVTSW, + CVTWS, + CLTS, + CLES, + CEQS, + BC1F, + BC1T, + BC1FL, + BC1TL, + MULAS, + MADDAS, + ADDAS, + MSUBAS, + MADDS, + MSUBS, + MOVS, + SQRTS, + RSQRTS, + + // COP2 + VMOVE, + VFTOI0, + VFTOI4, + VFTOI12, + VITOF0, + VITOF12, + VITOF15, + VABS, + + VADD, + VSUB, + VMUL, + VMINI, + VMAX, + VOPMSUB, + VMADD, + VMSUB, + + VADD_BC, + VSUB_BC, + VMUL_BC, + VMULA_BC, + VMADD_BC, + VADDA_BC, + VMADDA_BC, + VMSUBA_BC, + VMSUB_BC, + VMINI_BC, + VMAX_BC, + + VADDQ, + VSUBQ, + VMULQ, + VMSUBQ, + + VMULA, + VADDA, + VMADDA, + + VOPMULA, + VDIV, + VCLIP, + VMULAQ, + + VMTIR, + VIAND, + VLQI, + VIADDI, + VSQI, + + VRGET, + + VSQRT, + VRSQRT, + + VRXOR, + VRNEXT, + VNOP, + VWAITQ, + VCALLMS, + + EE_OP_MAX +}; + +enum class FieldType { + RS, + RT, + RD, + SA, + FT, + FS, + FD, + SYSCALL, + SIMM16, + ZIMM16, + PCR, + DEST, + BC, + IMM5, + IMM15, + IL, + ZERO +}; + +enum class DecodeType { + GPR, + IMM, + FPR, + COP0, + COP2, + PCR, + VF, + VI, + BRANCH_TARGET, + VCALLMS_TARGET, + DEST, + BC, + VU_Q, + VU_ACC, + IL +}; + +struct DecodeStep { + bool is_src = false; + FieldType field; + DecodeType decode; +}; + +constexpr int MAX_DECODE_STEPS = 5; + +struct OpcodeInfo { + std::string name; + + bool is_branch = false; + bool is_branch_likely = false; + bool can_lo16_link = false; + bool defined = false; + bool is_store = false; + bool is_load = false; + bool has_delay_slot = false; + + void step(DecodeStep& s); + + OpcodeInfo& src(FieldType field, DecodeType decode); + OpcodeInfo& src_gpr(FieldType field); + OpcodeInfo& src_fpr(FieldType field); + OpcodeInfo& src_vf(FieldType field); + OpcodeInfo& src_vi(FieldType field); + + OpcodeInfo& dst(FieldType field, DecodeType decode); + OpcodeInfo& dst_gpr(FieldType field); + OpcodeInfo& dst_fpr(FieldType field); + OpcodeInfo& dst_vf(FieldType field); + OpcodeInfo& dst_vi(FieldType field); + + uint8_t step_count; + DecodeStep steps[MAX_DECODE_STEPS]; +}; + +extern OpcodeInfo gOpcodeInfo[(uint32_t)InstructionKind::EE_OP_MAX]; + +void init_opcode_info(); + +#endif // NEXT_OPCODEINFO_H diff --git a/decompiler/Disasm/Register.cpp b/decompiler/Disasm/Register.cpp new file mode 100644 index 000000000..4c8eb53c2 --- /dev/null +++ b/decompiler/Disasm/Register.cpp @@ -0,0 +1,215 @@ +/*! + * @file Register.cpp + * Representation of an EE register. + */ + +#include "Register.h" +#include + +//////////////////////////// +// Register Name Constants +//////////////////////////// + +const static char* gpr_names[32] = { + "r0", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", + "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"}; + +const static char* fpr_names[32] = {"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", + "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", + "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", + "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31"}; + +const static char* cop0_names[32] = { + "Index", "Random", "EntryLo0", "EntryLo1", "Context", "PageMask", "Wired", + "INVALID7", "BadVAddr", "Count", "EntryHi", "Compare", "Status", "Cause", + "EPC", "PRId", "Config", "INVALID17", "INVALID18", "INVALID19", "INVALID20", + "INVALID21", "INVALID22", "BadPAddr", "Debug", "Perf", "INVALID26", "INVALID27", + "TagLo", "TagHi", "ErrorEPR", "INVALID31"}; + +const static char* vf_names[32] = {"vf0", "vf1", "vf2", "vf3", "vf4", "vf5", "vf6", "vf7", + "vf8", "vf9", "vf10", "vf11", "vf12", "vf13", "vf14", "vf15", + "vf16", "vf17", "vf18", "vf19", "vf20", "vf21", "vf22", "vf23", + "vf24", "vf25", "vf26", "vf27", "vf28", "vf29", "vf30", "vf31"}; + +const static char* vi_names[32] = { + "vi0", "vi1", "vi2", "vi3", "vi4", "vi5", "vi6", "vi7", + "vi8", "vi9", "vi10", "vi11", "vi12", "vi13", "vi14", "vi15", + "Status", "MAC", "Clipping", "INVALID3", "R", "I", "Q", "INVALID7", + "INVALID8", "INVALID9", "TPC", "CMSAR0", "FBRST", "VPU-STAT", "INVALID14", "CMSAR1"}; + +const static char* pcr_names[2] = {"pcr0", "pcr1"}; + +///////////////////////////// +// Register Names Conversion +///////////////////////////// + +namespace { +const char* gpr_to_charp(Reg::Gpr gpr) { + assert(gpr < 32); + return gpr_names[gpr]; +} + +const char* fpr_to_charp(uint32_t fpr) { + assert(fpr < 32); + return fpr_names[fpr]; +} + +const char* cop0_to_charp(Reg::Cop0 cpr) { + assert(cpr < 32); + return cop0_names[cpr]; +} + +const char* vf_to_charp(uint32_t vf) { + assert(vf < 32); + return vf_names[vf]; +} + +const char* vi_to_charp(uint32_t vi) { + assert(vi < 32); + return vi_names[vi]; +} + +const char* pcr_to_charp(uint32_t pcr) { + assert(pcr < 2); + return pcr_names[pcr]; +} +} // namespace + +///////////////////////////// +// Register Class +///////////////////////////// +// A register is stored as a 16-bit integer, with the top 8 bits indicating the "kind" and the lower +// 8 bits representing the register id within that kind. If the integer is -1, it is a special +// "invalid" register used to represent an uninitialized Register. + +// Note: VI / COP2 are separate "kinds" of registers, each with 16 registers. +// It might make sense to make this a single "kind" instead? + +/*! + * Create a register. The kind and num must both be valid. + */ +Register::Register(Reg::RegisterKind kind, uint32_t num) { + id = (kind << 8) | num; + + // check range: + switch (kind) { + case Reg::GPR: + case Reg::FPR: + case Reg::VF: + case Reg::COP0: + case Reg::VI: + assert(num < 32); + break; + case Reg::PCR: + assert(num < 2); + break; + default: + assert(false); + } +} + +/*! + * Convert to string. The register must be valid. + */ +const char* Register::to_charp() const { + switch (get_kind()) { + case Reg::GPR: + return gpr_to_charp(get_gpr()); + case Reg::FPR: + return fpr_to_charp(get_fpr()); + case Reg::VF: + return vf_to_charp(get_vf()); + case Reg::VI: + return vi_to_charp(get_vi()); + case Reg::COP0: + return cop0_to_charp(get_cop0()); + case Reg::PCR: + return pcr_to_charp(get_pcr()); + default: + assert(false); + } +} + +/*! + * Convert to string. The register must be valid. + */ +std::string Register::to_string() const { + return {to_charp()}; +} + +/*! + * Get the register kind. + */ +Reg::RegisterKind Register::get_kind() const { + uint16_t kind = id >> 8; + assert(kind < Reg::MAX_KIND); + return (Reg::RegisterKind)kind; +} + +/*! + * Get the GPR number. Must be a GPR. + */ +Reg::Gpr Register::get_gpr() const { + assert(get_kind() == Reg::GPR); + uint16_t kind = id & 0xff; + assert(kind < Reg::MAX_GPR); + return (Reg::Gpr)(kind); +} + +/*! + * Get the FPR number. Must be an FPR. + */ +uint32_t Register::get_fpr() const { + assert(get_kind() == Reg::FPR); + uint16_t kind = id & 0xff; + assert(kind < 32); + return kind; +} + +/*! + * Get the VF number. Must be a VF. + */ +uint32_t Register::get_vf() const { + assert(get_kind() == Reg::VF); + uint16_t kind = id & 0xff; + assert(kind < 32); + return kind; +} + +/*! + * Get the VI number. Must be a VI. + */ +uint32_t Register::get_vi() const { + assert(get_kind() == Reg::VI); + uint16_t kind = id & 0xff; + assert(kind < 32); + return kind; +} + +/*! + * Get the COP0 number. Must be a COP0. + */ +Reg::Cop0 Register::get_cop0() const { + assert(get_kind() == Reg::COP0); + uint16_t kind = id & 0xff; + assert(kind < Reg::MAX_COP0); + return (Reg::Cop0)(kind); +} + +/*! + * Get the PCR number. Must be a PCR. + */ +uint32_t Register::get_pcr() const { + assert(get_kind() == Reg::PCR); + uint16_t kind = id & 0xff; + assert(kind < 2); + return kind; +} + +bool Register::operator==(const Register& other) const { + return id == other.id; +} + +bool Register::operator!=(const Register& other) const { + return id != other.id; +} \ No newline at end of file diff --git a/decompiler/Disasm/Register.h b/decompiler/Disasm/Register.h new file mode 100644 index 000000000..2a8196800 --- /dev/null +++ b/decompiler/Disasm/Register.h @@ -0,0 +1,145 @@ +/*! + * @file Register.h + * Representation of an EE register. + */ + +#ifndef NEXT_REGISTER_H +#define NEXT_REGISTER_H + +#include +#include + +// Namespace for register name constants +namespace Reg { +enum RegisterKind { + GPR = 0, // EE General purpose registers, these have nicknames. + FPR = 1, // EE Floating point registers, just called f0 - f31 + VF = 2, // VU0 Floating point vector registers from EE, just called vf0 - vf31 + VI = + 3, // VU0 Integer registers from EE, the first 16 are vi00 - vi15, the rest are control regs. + COP0 = 4, // EE COP0 Control Registers: full of fancy names (there are 32 of them) + PCR = 5, // Performance Counter registers (PCR0, PCR1) + MAX_KIND = 6 +}; + +// nicknames for GPRs +enum Gpr { + R0 = 0, // hardcoded to zero + AT = 1, // temp, not used by GOAL compiler, but used by GOAL's kernel inline assembly (an other + // places?) + V0 = 2, // return, temp + V1 = 3, // temp + A0 = 4, // arg0, temp + A1 = 5, // arg1, temp + A2 = 6, // arg2, temp + A3 = 7, // arg3, temp + T0 = 8, // arg4, temp + T1 = 9, // arg5, temp + T2 = 10, // arg6, temp + T3 = 11, // arg7, temp + T4 = 12, // temp + T5 = 13, // temp + T6 = 14, // temp + T7 = 15, // temp + S0 = 16, // saved + S1 = 17, // saved + S2 = 18, // saved + S3 = 19, // saved + S4 = 20, // saved + S5 = 21, // saved + S6 = 22, // process pointer + S7 = 23, // symbol table + T8 = 24, // temp + T9 = 25, // function pointer + K0 = 26, // reserved + K1 = 27, // reserved + GP = 28, // saved (C code uses this a global pointer) + SP = 29, // stack pointer + FP = 30, // global pointer (address of current function) + RA = 31, // return address + MAX_GPR = 32 +}; + +// nicknames for COP0 registers +enum Cop0 { + INDEX = 0, + RANDOM = 1, + ENTRYLO0 = 2, + ENTRYLO1 = 3, + CONTEXT = 4, + PAGEMASK = 5, + WIRED = 6, + INVALID7 = 7, + BADVADDR = 8, + COUNT = 9, + ENTRYHI = 10, + COMPARE = 11, + COP0_STATUS = 12, + CAUSE = 13, + EPC = 14, + PRID = 15, + CONFIG = 16, + INVALID17 = 17, + INVALID18 = 18, + INVALID19 = 19, + INVALID20 = 20, + INVALID21 = 21, + INVALID22 = 22, + BADPADDR = 23, + DEBUG = 24, + PERF = 25, + INVALID26 = 26, + INVALID27 = 27, + TAGLO = 28, + TAGHI = 29, + ERROREPC = 30, + INVALID31 = 31, + MAX_COP0 = 32 +}; + +// nicknames for COP2 Integer (VI) registers +// the first 16 are vi0 - vi15, so they don't have nicknames +enum Vi { + COP2_STATUS = 16, + MAC = 17, + CLIPPING = 18, + COP2_INVALID3 = 19, + R = 20, + I = 21, + Q = 22, + COP2_INVALID7 = 23, + COP2_INVALID8 = 24, + COP2_INVALID9 = 25, + TPC = 26, + CMSAR0 = 27, + FBRST = 28, + VPUSTAT = 29, + COP2_INVALID14 = 30, + CMSAR1 = 31, + MAX_COP2 = 32 +}; +} // namespace Reg + +// Representation of a register. Uses a 32-bit integer internally. +class Register { + public: + Register() = default; + Register(Reg::RegisterKind kind, uint32_t num); + const char* to_charp() const; + std::string to_string() const; + Reg::RegisterKind get_kind() const; + Reg::Gpr get_gpr() const; + uint32_t get_fpr() const; + uint32_t get_vf() const; + uint32_t get_vi() const; + Reg::Cop0 get_cop0() const; + uint32_t get_pcr() const; + + bool operator==(const Register& other) const; + bool operator!=(const Register& other) const; + + private: + uint16_t id = -1; +}; + +#endif // NEXT_REGISTER_H diff --git a/decompiler/Function/BasicBlocks.cpp b/decompiler/Function/BasicBlocks.cpp new file mode 100644 index 000000000..6c9850998 --- /dev/null +++ b/decompiler/Function/BasicBlocks.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "BasicBlocks.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "decompiler/Disasm/InstructionMatching.h" + +/*! + * Find all basic blocks in a function. + * All delay slot instructions are grouped with the branch instruction. + * This is done by finding all "dividers", which are after branch delay instructions and before + * branch destinations, then sorting them, ignoring duplicates, and creating the blocks. + */ +std::vector find_blocks_in_function(const LinkedObjectFile& file, + int seg, + const Function& func) { + std::vector basic_blocks; + + // note - the first word of a function is the "function" type and should go in any basic block + std::vector dividers = {0, int(func.instructions.size())}; + + for (int i = 0; i < int(func.instructions.size()); i++) { + const auto& instr = func.instructions.at(i); + const auto& instr_info = instr.get_info(); + + if (instr_info.is_branch || instr_info.is_branch_likely) { + // make sure the delay slot of this branch is included in the function + assert(i + func.start_word < func.end_word - 1); + // divider after delay slot + dividers.push_back(i + 2); + auto label_id = instr.get_label_target(); + assert(label_id != -1); + const auto& label = file.labels.at(label_id); + // should only jump to within our own function + assert(label.target_segment == seg); + assert(label.offset / 4 > func.start_word); + assert(label.offset / 4 < func.end_word - 1); + dividers.push_back(label.offset / 4 - func.start_word); + } + } + + std::sort(dividers.begin(), dividers.end()); + + for (size_t i = 0; i < dividers.size() - 1; i++) { + if (dividers[i] != dividers[i + 1]) { + basic_blocks.emplace_back(dividers[i], dividers[i + 1]); + assert(dividers[i] >= 0); + } + } + + return basic_blocks; +} \ No newline at end of file diff --git a/decompiler/Function/BasicBlocks.h b/decompiler/Function/BasicBlocks.h new file mode 100644 index 000000000..3605fe96e --- /dev/null +++ b/decompiler/Function/BasicBlocks.h @@ -0,0 +1,23 @@ +#ifndef JAK_DISASSEMBLER_BASICBLOCKS_H +#define JAK_DISASSEMBLER_BASICBLOCKS_H + +#include +#include + +#include "CfgVtx.h" + +class LinkedObjectFile; +class Function; + +struct BasicBlock { + int start_word; + int end_word; + + BasicBlock(int _start_word, int _end_word) : start_word(_start_word), end_word(_end_word) {} +}; + +std::vector find_blocks_in_function(const LinkedObjectFile& file, + int seg, + const Function& func); + +#endif // JAK_DISASSEMBLER_BASICBLOCKS_H diff --git a/decompiler/Function/CfgVtx.cpp b/decompiler/Function/CfgVtx.cpp new file mode 100644 index 000000000..e7531f8d1 --- /dev/null +++ b/decompiler/Function/CfgVtx.cpp @@ -0,0 +1,1754 @@ +#include +#include "decompiler/Disasm/InstructionMatching.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "CfgVtx.h" +#include "Function.h" + +///////////////////////////////////////// +/// CfgVtx +///////////////////////////////////////// + +/*! + * Make this vertex a child vertex of new_parent! + */ +void CfgVtx::parent_claim(CfgVtx* new_parent) { + parent = new_parent; + + // clear out all this junk - we don't need it now that we are a part of the "real" CFG! + next = nullptr; + prev = nullptr; + pred.clear(); + succ_ft = nullptr; + succ_branch = nullptr; +} + +/*! + * Replace reference to old_pred as a predecessor with new_pred. + * Errors if old_pred wasn't referenced. + */ +void CfgVtx::replace_pred_and_check(CfgVtx* old_pred, CfgVtx* new_pred) { + bool replaced = false; + for (auto& x : pred) { + if (x == old_pred) { + assert(!replaced); + x = new_pred; + replaced = true; + } + } + assert(replaced); +} + +/*! + * Replace references to old_succ with new_succ in the successors. + * Errors if old_succ wasn't replaced. + */ +void CfgVtx::replace_succ_and_check(CfgVtx* old_succ, CfgVtx* new_succ) { + bool replaced = false; + if (succ_branch == old_succ) { + succ_branch = new_succ; + replaced = true; + } + + if (succ_ft == old_succ) { + succ_ft = new_succ; + replaced = true; + } + + assert(replaced); +} + +/*! + * Replace references to old_preds with a single new_pred. + * Doesn't insert duplicates. + * Error if all old preds aren't found. + * If new_pred is nullptr, just removes the old preds without adding a new. + */ +void CfgVtx::replace_preds_with_and_check(std::vector old_preds, CfgVtx* new_pred) { + std::vector found(old_preds.size(), false); + + std::vector new_pred_list; + + for (auto* existing_pred : pred) { + bool match = false; + size_t idx = -1; + for (size_t i = 0; i < old_preds.size(); i++) { + if (existing_pred == old_preds[i]) { + assert(!match); + idx = i; + match = true; + } + } + + if (match) { + found.at(idx) = true; + } else { + new_pred_list.push_back(existing_pred); + } + } + + if (new_pred) { + new_pred_list.push_back(new_pred); + } + + pred = new_pred_list; + + for (auto x : found) { + assert(x); + } +} + +std::string CfgVtx::links_to_string() { + std::string result; + if (parent) { + result += " parent: " + parent->to_string() + "\n"; + } + + if (succ_branch) { + result += " succ_branch: " + succ_branch->to_string() + "\n"; + } + + if (succ_ft) { + result += " succ_ft: " + succ_ft->to_string() + "\n"; + } + + if (next) { + result += " next: " + next->to_string() + "\n"; + } + + if (prev) { + result += " prev: " + prev->to_string() + "\n"; + } + + if (!pred.empty()) { + result += " preds:\n"; + for (auto* x : pred) { + result += " " + x->to_string() + "\n"; + } + } + return result; +} + +///////////////////////////////////////// +/// VERTICES +///////////////////////////////////////// + +std::string BlockVtx::to_string() { + if (is_early_exit_block) { + return "Block (EA) " + std::to_string(block_id); + } else { + return "Block " + std::to_string(block_id); + } +} + +std::shared_ptr
BlockVtx::to_form() { + return toForm("b" + std::to_string(block_id)); +} + +std::string SequenceVtx::to_string() { + assert(!seq.empty()); + // todo - this is not a great way to print it. Maybe sequences should have an ID or name? + std::string result = + "Seq " + seq.front()->to_string() + " ... " + seq.back()->to_string() + std::to_string(uid); + return result; +} + +std::shared_ptr SequenceVtx::to_form() { + std::vector> forms; + forms.push_back(toForm("seq")); + for (auto* x : seq) { + forms.push_back(x->to_form()); + } + return buildList(forms); +} + +std::string EntryVtx::to_string() { + return "ENTRY"; +} + +std::shared_ptr EntryVtx::to_form() { + return toForm("entry"); +} + +std::string ExitVtx::to_string() { + return "EXIT"; +} + +std::shared_ptr ExitVtx::to_form() { + return toForm("exit"); +} + +std::string CondWithElse::to_string() { + return "CONDWE" + std::to_string(uid); +} + +std::shared_ptr CondWithElse::to_form() { + std::vector> forms; + forms.push_back(toForm("cond")); + for (const auto& x : entries) { + std::vector> e = {x.condition->to_form(), x.body->to_form()}; + forms.push_back(buildList(e)); + } + std::vector> e = {toForm("else"), else_vtx->to_form()}; + forms.push_back(buildList(e)); + return buildList(forms); +} + +std::string CondNoElse::to_string() { + return "CONDNE" + std::to_string(uid); +} + +std::shared_ptr CondNoElse::to_form() { + std::vector> forms; + forms.push_back(toForm("cond")); + for (const auto& x : entries) { + std::vector> e = {x.condition->to_form(), x.body->to_form()}; + forms.push_back(buildList(e)); + } + return buildList(forms); +} + +std::string WhileLoop::to_string() { + return "WHL" + std::to_string(uid); +} + +std::shared_ptr WhileLoop::to_form() { + std::vector> forms = {toForm("while"), condition->to_form(), + body->to_form()}; + return buildList(forms); +} + +std::string UntilLoop::to_string() { + return "UNTL" + std::to_string(uid); +} + +std::shared_ptr UntilLoop::to_form() { + std::vector> forms = {toForm("until"), condition->to_form(), + body->to_form()}; + return buildList(forms); +} + +std::string UntilLoop_single::to_string() { + return "UNTLS" + std::to_string(uid); +} + +std::shared_ptr UntilLoop_single::to_form() { + std::vector> forms = {toForm("until1"), block->to_form()}; + return buildList(forms); +} + +std::string InfiniteLoopBlock::to_string() { + return "INFL" + std::to_string(uid); +} + +std::shared_ptr InfiniteLoopBlock::to_form() { + std::vector> forms = {toForm("inf-loop"), block->to_form()}; + return buildList(forms); +} + +std::string ShortCircuit::to_string() { + return "SC" + std::to_string(uid); +} + +std::shared_ptr ShortCircuit::to_form() { + std::vector> forms; + forms.push_back(toForm("sc")); + for (const auto& x : entries) { + forms.push_back(x->to_form()); + } + return buildList(forms); +} + +/* +std::shared_ptr IfElseVtx::to_form() { + std::vector> forms = {toForm("if"), condition->to_form(), + true_case->to_form(), false_case->to_form()}; + return buildList(forms); +} + +std::string IfElseVtx::to_string() { + return "if_else"; // todo - something nicer +} +*/ + +std::string GotoEnd::to_string() { + return "goto_end" + std::to_string(uid); +} + +std::shared_ptr GotoEnd::to_form() { + std::vector> forms = {toForm("return-from-function"), body->to_form(), + unreachable_block->to_form()}; + return buildList(forms); +} + +ControlFlowGraph::ControlFlowGraph() { + // allocate the entry and exit vertices. + m_entry = alloc(); + m_exit = alloc(); +} + +ControlFlowGraph::~ControlFlowGraph() { + for (auto* x : m_node_pool) { + delete x; + } +} + +/*! + * Convert unresolved portion of CFG into a format that can be read by dot, a graph layout tool. + * This is intended to help with debugging why a cfg couldn't be resolved. + */ +std::string ControlFlowGraph::to_dot() { + std::string result = "digraph G {\n"; + std::string invis; + for (auto* node : m_node_pool) { + if (!node->parent) { + auto me = "\"" + node->to_string() + "\""; + if (!invis.empty()) { + invis += " -> "; + } + invis += me; + result += me + ";\n"; + + // it's a top level node + for (auto* s : node->succs()) { + result += me + " -> \"" + s->to_string() + "\";\n"; + } + } + } + result += "\n" + invis + " [style=invis];\n}\n"; + result += "\n\n"; + for_each_top_level_vtx([&](CfgVtx* vtx) { + result += "VTX: " + vtx->to_string() + "\n" + vtx->links_to_string() + "\n"; + return true; + }); + return result; +} + +/*! + * Is this CFG fully resolved? Did we succeed in decoding the control flow? + */ +bool ControlFlowGraph::is_fully_resolved() { + return get_top_level_vertices_count() == 1; +} + +/*! + * How many top level vertices are there? Doesn't count entry and exit. + */ +int ControlFlowGraph::get_top_level_vertices_count() { + int count = 0; + for (auto* x : m_node_pool) { + if (!x->parent && x != entry() && x != exit()) { + count++; + } + } + return count; +} + +/*! + * Get the top level vertex. Only safe to call if we are fully resolved. + */ +CfgVtx* ControlFlowGraph::get_single_top_level() { + assert(get_top_level_vertices_count() == 1); + for (auto* x : m_node_pool) { + if (!x->parent && x != entry() && x != exit()) { + return x; + } + } + assert(false); + return nullptr; +} + +/*! + * Turn into a form. If fully resolved, prints the nested control flow. Otherwise puts all the + * ungrouped stuff into an "(ungrouped ...)" form and prints that. + */ +std::shared_ptr ControlFlowGraph::to_form() { + if (get_top_level_vertices_count() == 1) { + return get_single_top_level()->to_form(); + } else { + std::vector> forms = {toForm("ungrouped")}; + for (auto* x : m_node_pool) { + if (!x->parent && x != entry() && x != exit()) { + forms.push_back(x->to_form()); + } + } + return buildList(forms); + } +} + +/*! + * Turn into a string. If fully resolved, prints the nested control flow. Otherwise puts all the + * ungrouped stuff into an "(ungrouped ...)" form and prints that. + */ +std::string ControlFlowGraph::to_form_string() { + // todo - fix bug in pretty printing and reduce this to 80! + return to_form()->toStringPretty(0, 140); +} + +// bool ControlFlowGraph::compact_top_level() { +// int compact_count = 0; +// +// std::string orig = to_dot(); +// +// while (compact_one_in_top_level()) { +// compact_count++; +// } +// +// if (compact_count) { +// printf("%s\nCHANGED TO\n%s\n", orig.c_str(), to_dot().c_str()); +// return true; +// } +// +// return false; +//} +// +// bool ControlFlowGraph::compact_one_in_top_level() { +// for (auto* node : m_node_pool) { +// if (node->parent_container) { +// continue; +// } +// +// if (node != entry() && node->succ.size() == 1 && !node->has_succ(exit()) && +// node->succ.front()->pred.size() == 1 && !node->succ.front()->has_succ(node)) { +// // can compact! +// auto first = node; +// auto second = node->succ.front(); +// assert(second->has_pred(first)); +// +// make_sequence(first, second); +// return true; +// } +// } +// +// return false; +//} +// + +// bool ControlFlowGraph::is_if_else(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2, CfgVtx* b3) { +// // check existance +// if (!b0 || !b1 || !b2 || !b3) +// return false; +// +// // check verts +// if (b0->next != b1) +// return false; +// if (b0->succ_ft != b1) +// return false; +// if (b0->succ_branch != b2) +// return false; +// if (b0->end_branch.branch_always) +// return false; +// if (b0->end_branch.branch_likely) +// return false; +// assert(b0->end_branch.has_branch); +// // b0 prev, pred don't care +// +// if (b1->prev != b0) +// return false; +// if (!b1->has_pred(b0)) +// return false; +// if (b1->pred.size() != 1) +// return false; +// if (b1->next != b2) +// return false; +// if (b1->succ_ft) +// return false; +// if (b1->succ_branch != b3) +// return false; +// assert(b1->end_branch.branch_always); +// assert(b1->end_branch.has_branch); +// if (b1->end_branch.branch_likely) +// return false; +// +// if (b2->prev != b1) +// return false; +// if (!b2->has_pred(b0)) +// return false; +// if (b2->pred.size() != 1) +// return false; +// if (b2->next != b3) +// return false; +// if (b2->succ_branch) +// return false; +// assert(!b2->end_branch.has_branch); +// if (b2->succ_ft != b3) +// return false; +// +// if (b3->prev != b2) +// return false; +// if (!b3->has_pred(b2)) +// return false; +// if (!b3->has_pred(b1)) +// return false; +// +// return true; +//} +// +bool ControlFlowGraph::is_while_loop(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2) { + // todo - check delay slots! + if (!b0 || !b1 || !b2) + return false; + + // check next and prev + if (b0->next != b1) + return false; + if (b1->next != b2) + return false; + if (b2->prev != b1) + return false; + if (b1->prev != b0) + return false; + + // // check branch to condition at the beginning + if (b0->succ_ft) + return false; + if (b0->succ_branch != b2) + return false; + assert(b0->end_branch.has_branch); + assert(b0->end_branch.branch_always); + if (b0->end_branch.branch_likely) + return false; + + // check b1 -> b2 fallthrough + if (b1->succ_ft != b2) + return false; + if (b1->succ_branch) + return false; + assert(!b1->end_branch.has_branch); + if (!b2->has_pred(b0)) { + printf("expect b2 (%s) to have pred b0 (%s)\n", b2->to_string().c_str(), + b0->to_string().c_str()); + printf("but it doesn't! instead it has:\n"); + for (auto* x : b2->pred) { + printf(" %s\n", x->to_string().c_str()); + } + if (b0->succ_ft) { + printf("b0's succ_ft: %s\n", b0->succ_ft->to_string().c_str()); + } + if (b0->succ_branch) { + printf("b0's succ_branch: %s\n", b0->succ_branch->to_string().c_str()); + } + } + assert(b2->has_pred(b0)); + assert(b2->has_pred(b1)); + if (b2->pred.size() != 2) + return false; + + // check b2's branch back + if (b2->succ_branch != b1) + return false; + if (b2->end_branch.branch_likely) + return false; + if (b2->end_branch.branch_always) + return false; + + return true; +} + +bool ControlFlowGraph::is_until_loop(CfgVtx* b1, CfgVtx* b2) { + // todo - check delay slots! + if (!b1 || !b2) + return false; + + // check next and prev + if (b1->next != b2) + return false; + if (b2->prev != b1) + return false; + + // check b1 -> b2 fallthrough + if (b1->succ_ft != b2) + return false; + if (b1->succ_branch) + return false; + assert(!b1->end_branch.has_branch); + + assert(b2->has_pred(b1)); + if (b2->pred.size() != 1) + return false; + + // check b2's branch back + if (b2->succ_branch != b1) + return false; + if (b2->end_branch.branch_likely) + return false; + if (b2->end_branch.branch_always) + return false; + + return true; +} + +bool ControlFlowGraph::is_goto_end_and_unreachable(CfgVtx* b0, CfgVtx* b1) { + if (!b0 || !b1) { + return false; + } + + // b0 should branch to the and end vertex always + if (!b0->end_branch.has_branch) + return false; + if (!b0->end_branch.branch_always) + return false; + if (b0->end_branch.branch_likely) + return false; + + // b0 should next to b1 + if (b0->next != b1) + return false; + assert(b1->prev == b0); + + // b1 should have no preds + if (!b1->pred.empty()) + return false; + + auto dest = b0->succ_branch; + auto dest_as_block = dynamic_cast(dest); + // printf("DAB: %s\n", dest_as_block->to_string().c_str()); + if (!dest_as_block) + return false; + if (!dest_as_block->is_early_exit_block) + return false; + + return true; // match! +} + +/* +bool ControlFlowGraph::find_if_else_top_level() { + // todo check delay slots + // Example: + // B0: + // beq s7, v1, B2 ;; inverted branch condition (branch on condition not met) + // sll r0, r0, 0 ;; nop in delay slot + // B1: + // true case! + // beq r0, r0, B3 ;; unconditional branch + // sll r0, r0, 0 ;; nop in delay slot + // B2: + // false case! ;; fall through + // B3: + // rest of code + bool found_one = false; + bool needs_work = true; + while (needs_work) { + needs_work = false; // until we change something, assume we're done. + + for_each_top_level_vtx([&](CfgVtx* vtx) { + // return true means "try again with a different vertex" + // return false means "I changed something, bail out so we can start from the beginning + // again." + + // attempt to match b0, b1, b2, b3 + auto* b0 = vtx; + auto* b1 = vtx->succ_ft; + auto* b2 = vtx->succ_branch; + auto* b3 = b2 ? b2->succ_ft : nullptr; + + if (is_if_else(b0, b1, b2, b3)) { + needs_work = true; + + // create the new vertex! + auto* new_vtx = alloc(); + new_vtx->condition = b0; + new_vtx->true_case = b1; + new_vtx->false_case = b2; + + // link new vertex pred + for (auto* new_pred : b0->pred) { + new_pred->replace_succ_and_check(b0, new_vtx); + } + new_vtx->pred = b0->pred; + + // link new vertex succ + b3->replace_preds_with_and_check({b1, b2}, new_vtx); + new_vtx->succ_ft = b3; + + // setup next/prev + new_vtx->prev = b0->prev; + if (new_vtx->prev) { + new_vtx->prev->next = new_vtx; + } + new_vtx->next = b3; + b3->prev = new_vtx; + + b0->parent_claim(new_vtx); + b1->parent_claim(new_vtx); + b2->parent_claim(new_vtx); + found_one = true; + return false; + } else { + return true; // try again! + } + }); + } + return found_one; +} + */ + +bool ControlFlowGraph::find_while_loop_top_level() { + // B0 can start with whatever + // B0 ends in unconditional branch to B2 (condition). + // B2 has conditional non-likely branch to B1 + // B1 falls through to B2 and nowhere else + // B2 can end with whatever + bool found_one = false; + bool needs_work = true; + while (needs_work) { + needs_work = false; + for_each_top_level_vtx([&](CfgVtx* vtx) { + auto* b0 = vtx; + auto* b1 = vtx->next; + auto* b2 = b1 ? b1->next : nullptr; + + if (is_while_loop(b0, b1, b2)) { + needs_work = true; + + auto* new_vtx = alloc(); + new_vtx->body = b1; + new_vtx->condition = b2; + + b0->replace_succ_and_check(b2, new_vtx); + new_vtx->pred = {b0}; + + assert(b2->succ_ft); + b2->succ_ft->replace_pred_and_check(b2, new_vtx); + new_vtx->succ_ft = b2->succ_ft; + // succ_branch is going back into the loop + + new_vtx->prev = b0; + b0->next = new_vtx; + + new_vtx->next = b2->next; + if (new_vtx->next) { + new_vtx->next->prev = new_vtx; + } + + b1->parent_claim(new_vtx); + b2->parent_claim(new_vtx); + found_one = true; + return false; + } else { + return true; + } + }); + } + return found_one; +} + +bool ControlFlowGraph::find_until_loop() { + // B2 has conditional non-likely branch to B1 + // B1 falls through to B2 and nowhere else + // B2 can end with whatever + bool found_one = false; + bool needs_work = true; + while (needs_work) { + needs_work = false; + for_each_top_level_vtx([&](CfgVtx* vtx) { + auto* b1 = vtx; + auto* b2 = b1 ? b1->next : nullptr; + + if (is_until_loop(b1, b2)) { + needs_work = true; + + auto* new_vtx = alloc(); + new_vtx->body = b1; + new_vtx->condition = b2; + + for (auto* b0 : b1->pred) { + b0->replace_succ_and_check(b1, new_vtx); + } + + new_vtx->pred = b1->pred; + new_vtx->replace_preds_with_and_check({b2}, nullptr); + + assert(b2->succ_ft); + b2->succ_ft->replace_pred_and_check(b2, new_vtx); + new_vtx->succ_ft = b2->succ_ft; + // succ_branch is going back into the loop + + new_vtx->prev = b1->prev; + if (new_vtx->prev) { + new_vtx->prev->next = new_vtx; + } + + new_vtx->next = b2->next; + if (new_vtx->next) { + new_vtx->next->prev = new_vtx; + } + + b1->parent_claim(new_vtx); + b2->parent_claim(new_vtx); + found_one = true; + return false; + } else { + return true; + } + }); + } + return found_one; +} + +bool ControlFlowGraph::find_infinite_loop() { + bool found = false; + + for_each_top_level_vtx([&](CfgVtx* vtx) { + if (vtx->succ_branch == vtx && !vtx->succ_ft) { + auto inf = alloc(); + inf->block = vtx; + inf->pred = vtx->pred; + inf->replace_preds_with_and_check({vtx}, nullptr); + for (auto* x : inf->pred) { + x->replace_succ_and_check(vtx, inf); + } + inf->prev = vtx->prev; + if (inf->prev) { + inf->prev->next = inf; + } + + inf->next = vtx->next; + if (inf->next) { + inf->succ_ft = inf->next; + inf->next->prev = inf; + inf->succ_ft->pred.push_back(inf); + } + + inf->succ_branch = nullptr; + vtx->parent_claim(inf); + + found = true; + return false; + } + + return true; + }); + + return found; +} + +bool ControlFlowGraph::find_until1_loop() { + bool found = false; + + for_each_top_level_vtx([&](CfgVtx* vtx) { + if (vtx->succ_branch == vtx && vtx->succ_ft) { + auto loop = alloc(); + loop->block = vtx; + loop->pred = vtx->pred; + loop->replace_preds_with_and_check({vtx}, nullptr); + for (auto* x : loop->pred) { + x->replace_succ_and_check(vtx, loop); + } + loop->prev = vtx->prev; + if (loop->prev) { + loop->prev->next = loop; + } + + loop->next = vtx->next; + if (loop->next) { + loop->next->prev = loop; + } + + loop->succ_ft = vtx->succ_ft; + loop->succ_ft->replace_pred_and_check(vtx, loop); + + vtx->parent_claim(loop); + + found = true; + return false; + } + + return true; + }); + + return found; +} + +bool ControlFlowGraph::find_goto_end() { + bool replaced = false; + + for_each_top_level_vtx([&](CfgVtx* vtx) { + auto* b0 = vtx; + auto* b1 = vtx->next; + if (is_goto_end_and_unreachable(b0, b1)) { + replaced = true; + + auto* new_goto = alloc(); + new_goto->body = b0; + new_goto->unreachable_block = b1; + + for (auto* new_pred : b0->pred) { + // printf("fix up pred %s of %s\n", new_pred->to_string().c_str(), + // b0->to_string().c_str()); + new_pred->replace_succ_and_check(b0, new_goto); + } + new_goto->pred = b0->pred; + + for (auto* new_succ : b1->succs()) { + // new_succ->replace_preds_with_and_check({b1}, nullptr); + new_succ->replace_pred_and_check(b1, new_goto); + } + // this is a lie, but ok + + new_goto->succ_ft = b1->succ_ft; + new_goto->succ_branch = b1->succ_branch; + new_goto->end_branch = b1->end_branch; + + // if(b1->next) { + // b1->next->pred.push_back(new_goto); + // } + // new_goto->succ_branch = b1->succ_branch; + // new_goto->end_branch = b1->end_branch; + + new_goto->prev = b0->prev; + if (new_goto->prev) { + new_goto->prev->next = new_goto; + } + + new_goto->next = b1->next; + if (new_goto->next) { + new_goto->next->prev = new_goto; + } + + b0->succ_branch->replace_preds_with_and_check({b0}, nullptr); + + b0->parent_claim(new_goto); + b1->parent_claim(new_goto); + + return false; + } + + // keep looking + return true; + }); + + return replaced; +} + +bool ControlFlowGraph::is_sequence(CfgVtx* b0, CfgVtx* b1) { + if (!b0 || !b1) + return false; + if (b0->next != b1) + return false; + if (b0->succ_ft != b1) { + // may unconditionally branch to get to a loop. + if (b0->succ_branch != b1) + return false; + if (b0->succ_ft) + return false; + assert(b0->end_branch.branch_always); + } else { + // falls through + if (b0->succ_branch) + return false; + assert(!b0->end_branch.has_branch); + } + + if (b1->prev != b0) + return false; + if (b1->pred.size() != 1) + return false; + if (!b1->has_pred(b0)) + return false; + if (b1->succ_branch == b0) + return false; + + return true; +} + +bool ControlFlowGraph::is_sequence_of_non_sequences(CfgVtx* b0, CfgVtx* b1) { + if (!b0 || !b1) + return false; + if (dynamic_cast(b0) || dynamic_cast(b1)) + return false; + return is_sequence(b0, b1); +} + +bool ControlFlowGraph::is_sequence_of_sequence_and_non_sequence(CfgVtx* b0, CfgVtx* b1) { + if (!b0 || !b1) + return false; + if (!dynamic_cast(b0)) + return false; + if (dynamic_cast(b1)) + return false; + return is_sequence(b0, b1); +} + +bool ControlFlowGraph::is_sequence_of_sequence_and_sequence(CfgVtx* b0, CfgVtx* b1) { + if (!b0 || !b1) + return false; + if (!dynamic_cast(b0)) + return false; + if (!dynamic_cast(b1)) + return false; + return is_sequence(b0, b1); +} + +bool ControlFlowGraph::is_sequence_of_non_sequence_and_sequence(CfgVtx* b0, CfgVtx* b1) { + if (!b0 || !b1) { + return false; + } + + if (dynamic_cast(b0)) + return false; + if (!dynamic_cast(b1)) + return false; + return is_sequence(b0, b1); +} + +/*! + * Find and insert at most one sequence. Return true if sequence is inserted. + * To generate more readable debug output, we should aim to run this as infrequent and as + * late as possible, to avoid condition vertices with tons of extra junk packed in. + */ +bool ControlFlowGraph::find_seq_top_level() { + bool replaced = false; + for_each_top_level_vtx([&](CfgVtx* vtx) { + auto* b0 = vtx; + auto* b1 = vtx->next; + + // if (b0 && b1) { + // printf("try seq %s %s\n", b0->to_string().c_str(), b1->to_string().c_str()); + // } + + if (is_sequence_of_non_sequences(b0, b1)) { // todo, avoid nesting sequences. + // printf("make seq type 1 %s %s\n", b0->to_string().c_str(), b1->to_string().c_str()); + replaced = true; + + auto* new_seq = alloc(); + new_seq->seq.push_back(b0); + new_seq->seq.push_back(b1); + + for (auto* new_pred : b0->pred) { + new_pred->replace_succ_and_check(b0, new_seq); + } + new_seq->pred = b0->pred; + + for (auto* new_succ : b1->succs()) { + new_succ->replace_pred_and_check(b1, new_seq); + } + new_seq->succ_ft = b1->succ_ft; + new_seq->succ_branch = b1->succ_branch; + + new_seq->prev = b0->prev; + if (new_seq->prev) { + new_seq->prev->next = new_seq; + } + new_seq->next = b1->next; + if (new_seq->next) { + new_seq->next->prev = new_seq; + } + + b0->parent_claim(new_seq); + b1->parent_claim(new_seq); + new_seq->end_branch = b1->end_branch; + return false; + } + + if (is_sequence_of_sequence_and_non_sequence(b0, b1)) { + // printf("make seq type 2 %s %s\n", b0->to_string().c_str(), b1->to_string().c_str()); + replaced = true; + auto* seq = dynamic_cast(b0); + assert(seq); + + seq->seq.push_back(b1); + + for (auto* new_succ : b1->succs()) { + new_succ->replace_pred_and_check(b1, b0); + } + seq->succ_ft = b1->succ_ft; + seq->succ_branch = b1->succ_branch; + seq->next = b1->next; + if (seq->next) { + seq->next->prev = seq; + } + + b1->parent_claim(seq); + seq->end_branch = b1->end_branch; + return false; + } + + if (is_sequence_of_non_sequence_and_sequence(b0, b1)) { + replaced = true; + auto* seq = dynamic_cast(b1); + assert(seq); + seq->seq.insert(seq->seq.begin(), b0); + + for (auto* p : b0->pred) { + p->replace_succ_and_check(b0, seq); + } + seq->pred = b0->pred; + seq->prev = b0->prev; + if (seq->prev) { + seq->prev->next = seq; + } + + b0->parent_claim(seq); + return false; + } + + if (is_sequence_of_sequence_and_sequence(b0, b1)) { + // printf("make seq type 3 %s %s\n", b0->to_string().c_str(), b1->to_string().c_str()); + replaced = true; + auto* seq = dynamic_cast(b0); + assert(seq); + + auto* old_seq = dynamic_cast(b1); + assert(old_seq); + + for (auto* x : old_seq->seq) { + x->parent_claim(seq); + seq->seq.push_back(x); + } + + for (auto* x : old_seq->succs()) { + // printf("fix preds of %s\n", x->to_string().c_str()); + x->replace_pred_and_check(old_seq, seq); + } + seq->succ_branch = old_seq->succ_branch; + seq->succ_ft = old_seq->succ_ft; + seq->end_branch = old_seq->end_branch; + seq->next = old_seq->next; + if (seq->next) { + seq->next->prev = seq; + } + + // todo - proper trash? + old_seq->parent_claim(seq); + + return false; + } + + return true; // keep looking + }); + + return replaced; +} + +namespace { + +// is a found after b? +bool is_found_after(CfgVtx* a, CfgVtx* b) { + b = b->next; + while (b) { + if (a == b) { + return true; + } + b = b->next; + } + return false; +} + +} // namespace + +bool ControlFlowGraph::find_cond_w_else() { + bool found = false; + + for_each_top_level_vtx([&](CfgVtx* vtx) { + // determine where the "else" block would be + auto* c0 = vtx; // first condition + auto* b0 = c0->next; // first body + if (!b0) { + return true; + } + + // printf("cwe try %s %s\n", c0->to_string().c_str(), b0->to_string().c_str()); + + // first condition should have the _option_ to fall through to first body + if (c0->succ_ft != b0) { + return true; + } + + // first body MUST unconditionally jump to else + if (b0->succ_ft || b0->end_branch.branch_likely) { + return true; + } + + if (b0->pred.size() != 1) { + return true; + } + + assert(b0->end_branch.has_branch); + assert(b0->end_branch.branch_always); + assert(b0->succ_branch); + + // TODO - check what's in the delay slot! + auto* end_block = b0->succ_branch; + if (!end_block) { + return true; + } + + if (!is_found_after(end_block, b0)) { + return true; + } + + auto* else_block = end_block->prev; + if (!else_block) { + return true; + } + + if (!is_found_after(else_block, b0)) { + return true; + } + + if (else_block->succ_branch) { + return true; + } + + if (else_block->succ_ft != end_block) { + return true; + } + assert(!else_block->end_branch.has_branch); + + std::vector entries = {{c0, b0}}; + auto* prev_condition = c0; + auto* prev_body = b0; + + // loop to try to grab all the cases up to the else, or reject if the inside is not sufficiently + // compact or if this is not actually a cond with else Note, we are responsible for checking the + // branch of prev_condition, but not the fallthrough + while (true) { + auto* next = prev_body->next; + if (next == else_block) { + // TODO - check what's in the delay slot! + // we're done! + // check the prev_condition, prev_body blocks properly go to the else/end_block + // prev_condition should jump to else: + if (prev_condition->succ_branch != else_block || prev_condition->end_branch.branch_likely) { + return true; + } + + // prev_body should jump to end + if (prev_body->succ_branch != end_block) { + return true; + } + + break; + } else { + auto* c = next; + auto* b = c->next; + if (!c || !b) { + ; + return true; + }; + // attempt to add another + + if (c->pred.size() != 1) { + return true; + } + + if (b->pred.size() != 1) { + return true; + } + + // how to get to cond + if (prev_condition->succ_branch != c || prev_condition->end_branch.branch_likely) { + return true; + } + + if (c->succ_ft != b) { + return true; // condition should have the option to fall through if matched + } + + // TODO - check what's in the delay slot! + if (c->end_branch.branch_likely) { + return true; // otherwise should go to next with a non-likely branch + } + + if (b->succ_ft || b->end_branch.branch_likely) { + return true; // body should go straight to else + } + + if (b->succ_branch != end_block) { + return true; + } + + entries.emplace_back(c, b); + prev_body = b; + prev_condition = c; + } + } + + // now we need to add it + // printf("got cwe\n"); + auto new_cwe = alloc(); + + // link x <-> new_cwe + for (auto* npred : c0->pred) { + npred->replace_succ_and_check(c0, new_cwe); + } + new_cwe->pred = c0->pred; + new_cwe->prev = c0->prev; + if (new_cwe->prev) { + new_cwe->prev->next = new_cwe; + } + + // link new_cwe <-> end + std::vector to_replace; + to_replace.push_back(else_block); + for (const auto& x : entries) { + to_replace.push_back(x.body); + } + end_block->replace_preds_with_and_check(to_replace, new_cwe); + new_cwe->succ_ft = end_block; + new_cwe->next = end_block; + end_block->prev = new_cwe; + + new_cwe->else_vtx = else_block; + new_cwe->entries = std::move(entries); + + else_block->parent_claim(new_cwe); + for (const auto& x : new_cwe->entries) { + x.body->parent_claim(new_cwe); + x.condition->parent_claim(new_cwe); + } + found = true; + return false; + }); + + return found; +} + +bool ControlFlowGraph::find_cond_n_else() { + bool found = false; + + for_each_top_level_vtx([&](CfgVtx* vtx) { + auto* c0 = vtx; // first condition + auto* b0 = c0->next; // first body + if (!b0) { + // printf("reject 0\n"); + return true; + } + + // printf("cne: c0 %s b0 %s\n", c0->to_string().c_str(), b0->to_string().c_str()); + + // first condition should have the _option_ to fall through to first body + if (c0->succ_ft != b0) { + // printf("reject 1\n"); + return true; + } + + // first body MUST unconditionally jump to end + bool single_case = false; + if (b0->end_branch.has_branch) { + if (b0->succ_ft || b0->end_branch.branch_likely) { + // printf("reject 2A\n"); + return true; + } + assert(b0->end_branch.has_branch); + assert(b0->end_branch.branch_always); + assert(b0->succ_branch); + } else { + single_case = true; + } + + if (b0->pred.size() != 1) { + // printf("reject 3\n"); + return true; + } + + // TODO - check what's in the delay slot! + auto* end_block = single_case ? b0->succ_ft : b0->succ_branch; + if (!end_block) { + // printf("reject 4"); + return true; + } + + if (!is_found_after(end_block, b0)) { + // printf("reject 5"); + return true; + } + + std::vector entries = {{c0, b0}}; + auto* prev_condition = c0; + auto* prev_body = b0; + + // loop to try to grab all the cases up to the else, or reject if the inside is not sufficiently + // compact or if this is not actually a cond with else Note, we are responsible for checking the + // branch of prev_condition, but not the fallthrough + while (true) { + auto* next = prev_body->next; + if (next == end_block) { + // TODO - check what's in the delay slot! + // we're done! + // check the prev_condition, prev_body blocks properly go to the else/end_block + // prev_condition should jump to else: + if (prev_condition->succ_branch != end_block || prev_condition->end_branch.branch_likely) { + // printf("reject 6\n"); + return true; + } + + // prev_body should jump to end + if (!single_case && prev_body->succ_branch != end_block) { + // printf("reject 7\n"); + return true; + } + + break; + } else { + auto* c = next; + auto* b = c->next; + if (!c || !b) { + // printf("reject 8\n"); + return true; + }; + // attempt to add another + // printf(" e %s %s\n", c->to_string().c_str(), b->to_string().c_str()); + + if (c->pred.size() != 1) { + // printf("reject 9\n"); + return true; + } + + if (b->pred.size() != 1) { + // printf("reject 10\n"); + return true; + } + + // how to get to cond + if (prev_condition->succ_branch != c || prev_condition->end_branch.branch_likely) { + // printf("reject 11\n"); + return true; + } + + if (c->succ_ft != b) { + // printf("reject 12\n"); + return true; // condition should have the option to fall through if matched + } + + // TODO - check what's in the delay slot! + if (c->end_branch.branch_likely) { + // printf("reject 13\n"); + return true; // otherwise should go to next with a non-likely branch + } + + if (b->succ_ft || b->end_branch.branch_likely) { + // printf("reject 14\n"); + return true; // body should go straight to else + } + + if (b->succ_branch != end_block) { + // printf("reject 14\n"); + return true; + } + + entries.emplace_back(c, b); + prev_body = b; + prev_condition = c; + } + } + + // now we need to add it + // printf("got cne\n"); + auto new_cwe = alloc(); + + // link x <-> new_cwe + for (auto* npred : c0->pred) { + // printf("in %s, replace succ %s with %s\n", npred->to_string().c_str(), + // c0->to_string().c_str(), new_cwe->to_string().c_str()); + npred->replace_succ_and_check(c0, new_cwe); + } + new_cwe->pred = c0->pred; + new_cwe->prev = c0->prev; + if (new_cwe->prev) { + new_cwe->prev->next = new_cwe; + } + + // link new_cwe <-> end + std::vector to_replace; + for (const auto& x : entries) { + to_replace.push_back(x.body); + } + to_replace.push_back(entries.back().condition); + // if(single_case) { + // to_replace.push_back(c0); + // } + end_block->replace_preds_with_and_check(to_replace, new_cwe); + new_cwe->succ_ft = end_block; + new_cwe->next = end_block; + end_block->prev = new_cwe; + + new_cwe->entries = std::move(entries); + + for (const auto& x : new_cwe->entries) { + x.body->parent_claim(new_cwe); + x.condition->parent_claim(new_cwe); + } + found = true; + + // printf("now %s\n", new_cwe->to_form()->toStringSimple().c_str()); + // printf("%s\n", to_dot().c_str()); + return false; + }); + + return found; +} + +bool ControlFlowGraph::find_short_circuits() { + bool found = false; + + for_each_top_level_vtx([&](CfgVtx* vtx) { + std::vector entries = {vtx}; + auto* end = vtx->succ_branch; + auto* next = vtx->next; + + // printf("try sc @ %s\n", vtx->to_string().c_str()); + if (!end || !vtx->end_branch.branch_likely || next != vtx->succ_ft) { + // printf("reject 1\n"); + return true; + } + + while (true) { + // printf("loop sc %s, end %s\n", vtx->to_string().c_str(), end->to_string().c_str()); + if (next == end) { + // one entry sc! + break; + } + + if (next->next == end) { + // check 1 pred + if (next->pred.size() != 1) { + // printf("reject 2\n"); + return true; + } + entries.push_back(next); + + // done! + break; + } + + // check 1 pred + if (next->pred.size() != 1) { + // printf("reject 3\n"); + return true; + } + + // check branch to end + if (next->succ_branch != end || !next->end_branch.branch_likely) { + // printf("reject 4\n"); + return true; + } + + // check fallthrough to next + if (!next->succ_ft) { + // printf("reject 5\n"); + return false; + } + + assert(next->succ_ft == next->next); // bonus check + entries.push_back(next); + next = next->succ_ft; + } + + // printf("got sc: \n"); + // for (auto* x : entries) { + // printf(" %s\n", x->to_string().c_str()); + // } + + auto new_sc = alloc(); + + for (auto* npred : vtx->pred) { + npred->replace_succ_and_check(vtx, new_sc); + } + new_sc->pred = vtx->pred; + new_sc->prev = vtx->prev; + if (new_sc->prev) { + new_sc->prev->next = new_sc; + } + + end->replace_preds_with_and_check(entries, new_sc); + new_sc->succ_ft = end; + new_sc->next = end; + end->prev = new_sc; + new_sc->entries = std::move(entries); + for (auto* x : new_sc->entries) { + x->parent_claim(new_sc); + } + found = true; + + return false; + }); + + return found; +} + +/*! + * Create vertices for basic blocks. Should only be called once to create all blocks at once. + * Will set up the next/prev relation for all of them, but not the pred/succ. + * The returned vector will have blocks in ordered, so the i-th entry is for the i-th block. + */ +const std::vector& ControlFlowGraph::create_blocks(int count) { + assert(m_blocks.empty()); + BlockVtx* prev = nullptr; // for linking next/prev + + for (int i = 0; i < count; i++) { + auto* new_block = alloc(i); + + // link next/prev + new_block->prev = prev; + if (prev) { + prev->next = new_block; + } + prev = new_block; + + m_blocks.push_back(new_block); + } + + return m_blocks; +} + +/*! + * Setup pred/succ for a block which falls through to the next. + */ +void ControlFlowGraph::link_fall_through(BlockVtx* first, BlockVtx* second) { + assert(!first->succ_ft); // don't want to overwrite something by accident. + // can only fall through to the next code in memory. + assert(first->next == second); + assert(second->prev == first); + first->succ_ft = second; + + if (!second->has_pred(first)) { + // if a block can block fall through and branch to the same block, we want to avoid adding + // it as a pred twice. This is rare, but does happen and makes sense with likely branches + // which only run the delay slot when taken. + second->pred.push_back(first); + } +} + +/*! + * Setup pred/succ for a block which branches to second. + */ +void ControlFlowGraph::link_branch(BlockVtx* first, BlockVtx* second) { + assert(!first->succ_branch); + + first->succ_branch = second; + if (!second->has_pred(first)) { + // see comment in link_fall_through + second->pred.push_back(first); + } +} + +void ControlFlowGraph::flag_early_exit(const std::vector& blocks) { + auto* b = m_blocks.back(); + const auto& block = blocks.at(b->block_id); + + if (block.start_word == block.end_word) { + b->is_early_exit_block = true; + assert(!b->end_branch.has_branch); + } +} + +/*! + * Build and resolve a Control Flow Graph as much as possible. + */ +std::shared_ptr build_cfg(const LinkedObjectFile& file, int seg, Function& func) { + printf("build cfg : %s\n", func.guessed_name.to_string().c_str()); + auto cfg = std::make_shared(); + + const auto& blocks = cfg->create_blocks(func.basic_blocks.size()); + + // add entry block + cfg->entry()->succ_ft = blocks.front(); + blocks.front()->pred.push_back(cfg->entry()); + + // add exit block + cfg->exit()->pred.push_back(blocks.back()); + blocks.back()->succ_ft = cfg->exit(); + + // todo - early returns! + + // set up succ / pred + for (int i = 0; i < int(func.basic_blocks.size()); i++) { + auto& b = func.basic_blocks[i]; + bool not_last = (i + 1) < int(func.basic_blocks.size()); + + if (b.end_word - b.start_word < 2) { + // there's no room for a branch here, fall through to the end + if (not_last) { + cfg->link_fall_through(blocks.at(i), blocks.at(i + 1)); + } + } else { + // might be a branch + int idx = b.end_word - 2; + assert(idx >= b.start_word); + auto& branch_candidate = func.instructions.at(idx); + + if (is_branch(branch_candidate, {})) { + blocks.at(i)->end_branch.has_branch = true; + blocks.at(i)->end_branch.branch_likely = is_branch(branch_candidate, true); + bool branch_always = is_always_branch(branch_candidate); + + // need to find block target + int block_target = -1; + int label_target = branch_candidate.get_label_target(); + assert(label_target != -1); + const auto& label = file.labels.at(label_target); + assert(label.target_segment == seg); + assert((label.offset % 4) == 0); + int offset = label.offset / 4 - func.start_word; + assert(offset >= 0); + + // the order here matters when there are zero size blocks. Unclear what the best answer is. + // i think in end it doesn't actually matter?? + // for (int j = 0; j < int(func.basic_blocks.size()); j++) { + for (int j = int(func.basic_blocks.size()); j-- > 0;) { + if (func.basic_blocks[j].start_word == offset) { + block_target = j; + break; + } + } + + assert(block_target != -1); + cfg->link_branch(blocks.at(i), blocks.at(block_target)); + + if (branch_always) { + // don't continue to the next one + blocks.at(i)->end_branch.branch_always = true; + } else { + // not an always branch + if (not_last) { + cfg->link_fall_through(blocks.at(i), blocks.at(i + 1)); + } + } + } else { + // not a branch at all + if (not_last) { + cfg->link_fall_through(blocks.at(i), blocks.at(i + 1)); + } + } + } + } + + cfg->flag_early_exit(func.basic_blocks); + + // if(func.guessed_name.to_string() == "(method 9 thread)") + // cfg->find_cond_w_else(); + + // if (func.guessed_name.to_string() != "looping-code") { + // return cfg; + // } + + bool changed = true; + while (changed) { + changed = false; + // note - we should prioritize finding short-circuiting expressions. + // printf("%s\n", cfg->to_dot().c_str()); + // printf("%s\n", cfg->to_form()->toStringPretty().c_str()); + + changed = changed | cfg->find_cond_w_else(); + changed = changed | cfg->find_cond_n_else(); + changed = changed || cfg->find_while_loop_top_level(); + // //// printf("while loops? %d\n", changed); + //// changed = changed || cfg->find_if_else_top_level(); + changed = changed || cfg->find_seq_top_level(); + changed = changed || cfg->find_short_circuits(); + + if (!changed) { + changed = changed || cfg->find_goto_end(); + changed = changed || cfg->find_until_loop(); + changed = changed || cfg->find_until1_loop(); + changed = changed || cfg->find_infinite_loop(); + }; + } + + if (!cfg->is_fully_resolved()) { + func.warnings += "Failed to fully resolve CFG\n"; + } + + return cfg; +} diff --git a/decompiler/Function/CfgVtx.h b/decompiler/Function/CfgVtx.h new file mode 100644 index 000000000..bf82158eb --- /dev/null +++ b/decompiler/Function/CfgVtx.h @@ -0,0 +1,336 @@ +#ifndef JAK_DISASSEMBLER_CFGVTX_H +#define JAK_DISASSEMBLER_CFGVTX_H + +#include +#include +#include +#include "decompiler/util/LispPrint.h" + +/*! + * In v, find an item equal to old, and replace it with replace. + * Will throw an error is there is not exactly one thing equal to old. + */ +template +void replace_exactly_one_in(std::vector& v, T old, T replace) { + bool replaced = false; + for (auto& x : v) { + if (x == old) { + assert(!replaced); + x = replace; + replaced = true; + } + } + assert(replaced); +} + +/*! + * Representation of a vertex in the control flow graph. + * + * The desired state of the control flow graph is to have a single "top-level" node, with NULL as + * its parent. This top level node can then be viewed as the entire control flow for the function. + * When the graph is fully understood, the only relation between vertices should be parent-child. + * For example, an "if_else" vertex will have a "condition" vertex, "true_case" vertex, and "false + * case" vertex as children. + * + * However, the initial state of the CFG is to have all the vertices be in the top level. When there + * are multiple top level vertices, the graph is considered to be "unresolved", as there are + * relations between these that are not explained by parent-child control structuring. These + * relations are either pred/succ, indicating program control flow, and next/prev indicating code + * layout order. These are undesirable because these do not map to high-level program structure. + * + * The graph attempts to "resolve" itself, meaning these pred/succ relations are destroyed and + * replaced with nested control flow. The pred/succ and next/prev relations should only exist at the + * top level. + * + * Once resolved, there will be a single "top level" node containing the entire control flow + * structure. + * + * All CfgVtxs should be created from the ControlFlowGraph::alloc function, which allocates them + * from a pool and cleans them up when the ControlFlowGraph is destroyed. This approach avoids + * circular reference issues from a referencing counting approach, but does mean that temporary + * allocations aren't cleaned up until the entire graph is deleted, but this is probably fine. + * + * Note - there are two special "top-level" vertices that are always present, called Entry and Exit. + * These always exist and don't count toward making the graph unresolved. + * These vertices won't be counted in the get_top_level_vertices_count. + * + * Desired end state of the graph: + * Entry -> some-top-level-control-flow-structure -> Exit + */ +class CfgVtx { + public: + virtual std::string to_string() = 0; // convert to a single line string for debugging + virtual std::shared_ptr to_form() = 0; // recursive print as LISP form. + virtual ~CfgVtx() = default; + + CfgVtx* parent = nullptr; // parent structure, or nullptr if top level + CfgVtx* succ_branch = nullptr; // possible successor from branching, or NULL if no branch + CfgVtx* succ_ft = nullptr; // possible successor from falling through, or NULL if impossible + CfgVtx* next = nullptr; // next code in memory + CfgVtx* prev = nullptr; // previous code in memory + std::vector pred; // all vertices which have us as succ_branch or succ_ft + int uid = -1; + + struct { + bool has_branch = false; // does the block end in a branch (any kind)? + bool branch_likely = false; // does the block end in a likely branch? + bool branch_always = false; // does the branch always get taken? + } end_branch; + + // each child class of CfgVtx will define its own children. + + /*! + * Do we have s as a successor? + */ + bool has_succ(CfgVtx* s) const { return succ_branch == s || succ_ft == s; } + + /*! + * Do we have p as a predecessor? + */ + bool has_pred(CfgVtx* p) const { + for (auto* x : pred) { + if (x == p) + return true; + } + return false; + } + + /*! + * Lazy function for getting all non-null succesors + */ + std::vector succs() { + std::vector result; + if (succ_branch) { + result.push_back(succ_branch); + } + if (succ_ft && succ_ft != succ_branch) { + result.push_back(succ_ft); + } + return result; + } + + void parent_claim(CfgVtx* new_parent); + void replace_pred_and_check(CfgVtx* old_pred, CfgVtx* new_pred); + void replace_succ_and_check(CfgVtx* old_succ, CfgVtx* new_succ); + void replace_preds_with_and_check(std::vector old_preds, CfgVtx* new_pred); + + std::string links_to_string(); +}; + +/*! + * Special Entry vertex representing the beginning of the function + */ +class EntryVtx : public CfgVtx { + public: + EntryVtx() = default; + std::shared_ptr to_form() override; + std::string to_string() override; +}; + +/*! + * Special Exit vertex representing the end of the function + */ +class ExitVtx : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; +}; + +/*! + * A vertex which represents a single basic block. It has no children. + */ +class BlockVtx : public CfgVtx { + public: + explicit BlockVtx(int id) : block_id(id) {} + std::string to_string() override; + std::shared_ptr to_form() override; + int block_id = -1; // which block are we? + bool is_early_exit_block = false; // are we an empty block at the end for early exits to jump to? +}; + +/*! + * A vertex representing a sequence of child vertices which are always represented in order. + * Child vertices in here don't set their next/prev pred/succ pointers as this counts as resolved. + */ +class SequenceVtx : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + std::vector seq; +}; + +/*! + * Representing a (cond ((a b) (c d) ... (else z))) structure. + * Note that the first condition ("a" in the above example) may "steal" instructions belonging + * to an outer scope and these may eventually need to be "unstolen" + */ +class CondWithElse : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + + struct Entry { + Entry() = default; + Entry(CfgVtx* _c, CfgVtx* _b) : condition(_c), body(_b) {} + CfgVtx* condition = nullptr; + CfgVtx* body = nullptr; + }; + + std::vector entries; + CfgVtx* else_vtx = nullptr; +}; + +/*! + * Representing a (cond ((a b) (c d) ... )) structure. + * Note that the first condition ("a" in the above example) may "steal" instructions belonging + * to an outer scope and these may eventually need to be "unstolen" + */ +class CondNoElse : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + + struct Entry { + Entry() = default; + Entry(CfgVtx* _c, CfgVtx* _b) : condition(_c), body(_b) {} + CfgVtx* condition = nullptr; + CfgVtx* body = nullptr; + }; + + std::vector entries; +}; + +class WhileLoop : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + + CfgVtx* condition = nullptr; + CfgVtx* body = nullptr; +}; + +class UntilLoop : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + + CfgVtx* condition = nullptr; + CfgVtx* body = nullptr; +}; + +class UntilLoop_single : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + + CfgVtx* block = nullptr; +}; + +class ShortCircuit : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + std::vector entries; +}; + +class InfiniteLoopBlock : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + CfgVtx* block; +}; + +class GotoEnd : public CfgVtx { + public: + std::string to_string() override; + std::shared_ptr to_form() override; + CfgVtx* body = nullptr; + CfgVtx* unreachable_block = nullptr; +}; + +struct BasicBlock; + +/*! + * The actual CFG class, which owns all the vertices. + */ +class ControlFlowGraph { + public: + ControlFlowGraph(); + ~ControlFlowGraph(); + + std::shared_ptr to_form(); + std::string to_form_string(); + std::string to_dot(); + int get_top_level_vertices_count(); + bool is_fully_resolved(); + CfgVtx* get_single_top_level(); + + void flag_early_exit(const std::vector& blocks); + + const std::vector& create_blocks(int count); + void link_fall_through(BlockVtx* first, BlockVtx* second); + void link_branch(BlockVtx* first, BlockVtx* second); + bool find_cond_w_else(); + bool find_cond_n_else(); + + // bool find_if_else_top_level(); + bool find_seq_top_level(); + bool find_while_loop_top_level(); + bool find_until_loop(); + bool find_until1_loop(); + bool find_short_circuits(); + bool find_goto_end(); + bool find_infinite_loop(); + + /*! + * Apply a function f to each top-level vertex. + * If f returns false, stops. + */ + template + void for_each_top_level_vtx(Func f) { + for (auto* x : m_node_pool) { + if (!x->parent && x != entry() && x != exit()) { + if (!f(x)) { + return; + } + } + } + } + + EntryVtx* entry() { return m_entry; } + ExitVtx* exit() { return m_exit; } + + /*! + * Allocate and construct a node of the specified type. + */ + template + T* alloc(Args&&... args) { + T* new_obj = new T(std::forward(args)...); + m_node_pool.push_back(new_obj); + new_obj->uid = m_uid++; + return new_obj; + } + + private: + // bool compact_one_in_top_level(); + // bool is_if_else(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2, CfgVtx* b3); + bool is_sequence(CfgVtx* b0, CfgVtx* b1); + bool is_sequence_of_non_sequences(CfgVtx* b0, CfgVtx* b1); + bool is_sequence_of_sequence_and_non_sequence(CfgVtx* b0, CfgVtx* b1); + bool is_sequence_of_sequence_and_sequence(CfgVtx* b0, CfgVtx* b1); + bool is_sequence_of_non_sequence_and_sequence(CfgVtx* b0, CfgVtx* b1); + bool is_while_loop(CfgVtx* b0, CfgVtx* b1, CfgVtx* b2); + bool is_until_loop(CfgVtx* b1, CfgVtx* b2); + bool is_goto_end_and_unreachable(CfgVtx* b0, CfgVtx* b1); + std::vector m_blocks; // all block nodes, in order. + std::vector m_node_pool; // all nodes allocated + EntryVtx* m_entry; // the entry vertex + ExitVtx* m_exit; // the exit vertex + int m_uid = 0; +}; + +class LinkedObjectFile; +class Function; +std::shared_ptr build_cfg(const LinkedObjectFile& file, int seg, Function& func); + +#endif // JAK_DISASSEMBLER_CFGVTX_H diff --git a/decompiler/Function/Function.cpp b/decompiler/Function/Function.cpp new file mode 100644 index 000000000..cba5ecf57 --- /dev/null +++ b/decompiler/Function/Function.cpp @@ -0,0 +1,552 @@ +#include +#include +#include "Function.h" +#include "decompiler/Disasm/InstructionMatching.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "decompiler/TypeSystem/TypeInfo.h" + +namespace { +std::vector gpr_backups = {make_gpr(Reg::GP), make_gpr(Reg::S5), make_gpr(Reg::S4), + make_gpr(Reg::S3), make_gpr(Reg::S2), make_gpr(Reg::S1), + make_gpr(Reg::S0)}; + +std::vector fpr_backups = {make_fpr(30), make_fpr(28), make_fpr(26), + make_fpr(24), make_fpr(22), make_fpr(20)}; + +Register get_expected_gpr_backup(int n, int total) { + assert(total <= int(gpr_backups.size())); + assert(n < total); + return gpr_backups.at((total - 1) - n); +} + +Register get_expected_fpr_backup(int n, int total) { + assert(total <= int(fpr_backups.size())); + assert(n < total); + return fpr_backups.at((total - 1) - n); +} + +uint32_t align16(uint32_t in) { + return (in + 15) & (~15); +} + +uint32_t align8(uint32_t in) { + return (in + 7) & (~7); +} + +uint32_t align4(uint32_t in) { + return (in + 3) & (~3); +} + +} // namespace + +Function::Function(int _start_word, int _end_word) : start_word(_start_word), end_word(_end_word) {} + +/*! + * Remove the function prologue from the first basic block and populate this->prologue with info. + */ +void Function::analyze_prologue(const LinkedObjectFile& file) { + int idx = 1; + + // first we look for daddiu sp, sp, -x to determine how much stack is used + if (is_gpr_2_imm_int(instructions.at(idx), InstructionKind::DADDIU, make_gpr(Reg::SP), + make_gpr(Reg::SP), {})) { + prologue.total_stack_usage = -instructions.at(idx).get_imm_src_int(); + idx++; + } else { + prologue.total_stack_usage = 0; + } + + // don't include type tag + prologue_end = 1; + + // if we use the stack, we may back up some registers onto it + if (prologue.total_stack_usage) { + // heuristics to detect asm functions + { + auto& instr = instructions.at(idx); + // storing stack pointer on the stack is done by some ASM kernel functions + if (instr.kind == InstructionKind::SW && instr.get_src(0).get_reg() == make_gpr(Reg::SP)) { + printf("[Warning] %s Suspected ASM function based on this instruction in prologue: %s\n", + guessed_name.to_string().c_str(), instr.to_string(file).c_str()); + warnings += "Flagged as ASM function because of " + instr.to_string(file) + "\n"; + suspected_asm = true; + return; + } + } + + // ra backup is always first + if (is_no_link_gpr_store(instructions.at(idx), 8, Register(Reg::GPR, Reg::RA), {}, + Register(Reg::GPR, Reg::SP))) { + prologue.ra_backed_up = true; + prologue.ra_backup_offset = get_gpr_store_offset_as_int(instructions.at(idx)); + assert(prologue.ra_backup_offset == 0); + idx++; + } + + { + auto& instr = instructions.at(idx); + + // storing s7 on the stack is done by interrupt handlers, which we probably don't want to + // support + if (instr.kind == InstructionKind::SD && instr.get_src(0).get_reg() == make_gpr(Reg::S7)) { + printf("[Warning] %s Suspected ASM function based on this instruction in prologue: %s\n", + guessed_name.to_string().c_str(), instr.to_string(file).c_str()); + warnings += "Flagged as ASM function because of " + instr.to_string(file) + "\n"; + suspected_asm = true; + return; + } + } + + // next is fp backup + if (is_no_link_gpr_store(instructions.at(idx), 8, Register(Reg::GPR, Reg::FP), {}, + Register(Reg::GPR, Reg::SP))) { + prologue.fp_backed_up = true; + prologue.fp_backup_offset = get_gpr_store_offset_as_int(instructions.at(idx)); + // in Jak 1 like we never backup fp unless ra is also backed up, so the offset is always 8. + // but it seems like it could be possible to do one without the other? + assert(prologue.fp_backup_offset == 8); + idx++; + + // after backing up fp, we always set it to t9. + prologue.fp_set = is_gpr_3(instructions.at(idx), InstructionKind::OR, make_gpr(Reg::FP), + make_gpr(Reg::T9), make_gpr(Reg::R0)); + assert(prologue.fp_set); + idx++; + } + + // next is gpr backups. these are in reverse order, so we should first find the length + // GOAL will always do the exact same thing when the same number of gprs needs to be backed up + // so we just need to determine the number of GPR backups, and we have all the info we need + int n_gpr_backups = 0; + int gpr_idx = idx; + bool expect_nothing_after_gprs = false; + + while (is_no_link_gpr_store(instructions.at(gpr_idx), 16, {}, {}, make_gpr(Reg::SP))) { + auto store_reg = instructions.at(gpr_idx).get_src(0).get_reg(); + + // sometimes stack memory is zeroed immediately after gpr backups, and this fools the previous + // check. + if (store_reg == make_gpr(Reg::R0)) { + printf( + "[Warning] %s Stack Zeroing Detected in Function::analyze_prologue, prologue may be " + "wrong\n", + guessed_name.to_string().c_str()); + warnings += "Stack Zeroing Detected, prologue may be wrong\n"; + expect_nothing_after_gprs = true; + break; + } + + // this also happens a few times per game. this a0/r0 check seems to be all that's needed to + // avoid false positives here! + if (store_reg == make_gpr(Reg::A0)) { + suspected_asm = true; + printf( + "[Warning] %s Suspected ASM function because register $a0 was stored on the stack!\n", + guessed_name.to_string().c_str()); + warnings += "a0 on stack detected, flagging as asm\n"; + return; + } + + n_gpr_backups++; + gpr_idx++; + } + + if (n_gpr_backups) { + prologue.gpr_backup_offset = get_gpr_store_offset_as_int(instructions.at(idx)); + for (int i = 0; i < n_gpr_backups; i++) { + int this_offset = get_gpr_store_offset_as_int(instructions.at(idx + i)); + auto this_reg = instructions.at(idx + i).get_src(0).get_reg(); + assert(this_offset == prologue.gpr_backup_offset + 16 * i); + if (this_reg != get_expected_gpr_backup(i, n_gpr_backups)) { + suspected_asm = true; + printf("[Warning] %s Suspected asm function that isn't flagged due to stack store %s\n", + guessed_name.to_string().c_str(), + instructions.at(idx + i).to_string(file).c_str()); + warnings += "Suspected asm function due to stack store: " + + instructions.at(idx + i).to_string(file) + "\n"; + return; + } + } + } + prologue.n_gpr_backup = n_gpr_backups; + idx = gpr_idx; + + int n_fpr_backups = 0; + int fpr_idx = idx; + if (!expect_nothing_after_gprs) { + // FPR backups + while (is_no_ll_fpr_store(instructions.at(fpr_idx), {}, {}, make_gpr(Reg::SP))) { + // auto store_reg = instructions.at(gpr_idx).get_src(0).get_reg(); + n_fpr_backups++; + fpr_idx++; + } + + if (n_fpr_backups) { + prologue.fpr_backup_offset = instructions.at(idx).get_src(1).get_imm(); + for (int i = 0; i < n_fpr_backups; i++) { + int this_offset = instructions.at(idx + i).get_src(1).get_imm(); + auto this_reg = instructions.at(idx + i).get_src(0).get_reg(); + assert(this_offset == prologue.fpr_backup_offset + 4 * i); + if (this_reg != get_expected_fpr_backup(i, n_fpr_backups)) { + suspected_asm = true; + printf("[Warning] %s Suspected asm function that isn't flagged due to stack store %s\n", + guessed_name.to_string().c_str(), + instructions.at(idx + i).to_string(file).c_str()); + warnings += "Suspected asm function due to stack store: " + + instructions.at(idx + i).to_string(file) + "\n"; + return; + } + } + } + } + prologue.n_fpr_backup = n_fpr_backups; + idx = fpr_idx; + + prologue_start = 1; + prologue_end = idx; + + prologue.stack_var_offset = 0; + if (prologue.ra_backed_up) { + prologue.stack_var_offset = 8; + } + if (prologue.fp_backed_up) { + prologue.stack_var_offset = 16; + } + + if (n_gpr_backups == 0 && n_fpr_backups == 0) { + prologue.n_stack_var_bytes = prologue.total_stack_usage - prologue.stack_var_offset; + } else if (n_gpr_backups == 0) { + // fprs only + prologue.n_stack_var_bytes = prologue.fpr_backup_offset - prologue.stack_var_offset; + } else if (n_fpr_backups == 0) { + // gprs only + prologue.n_stack_var_bytes = prologue.gpr_backup_offset - prologue.stack_var_offset; + } else { + // both, use gprs + assert(prologue.fpr_backup_offset > prologue.gpr_backup_offset); + prologue.n_stack_var_bytes = prologue.gpr_backup_offset - prologue.stack_var_offset; + } + + assert(prologue.n_stack_var_bytes >= 0); + + // check that the stack lines up by going in order + + // RA backup + int total_stack = 0; + if (prologue.ra_backed_up) { + total_stack = align8(total_stack); + assert(prologue.ra_backup_offset == total_stack); + total_stack += 8; + } + + if (!prologue.ra_backed_up && prologue.fp_backed_up) { + // GOAL does this for an unknown reason. + total_stack += 8; + } + + // FP backup + if (prologue.fp_backed_up) { + total_stack = align8(total_stack); + assert(prologue.fp_backup_offset == total_stack); + total_stack += 8; + assert(prologue.fp_set); + } + + // Stack Variables + if (prologue.n_stack_var_bytes) { + // no alignment because we don't know how the stack vars are aligned. + // stack var padding counts toward this section. + assert(prologue.stack_var_offset == total_stack); + total_stack += prologue.n_stack_var_bytes; + } + + // GPRS + if (prologue.n_gpr_backup) { + total_stack = align16(total_stack); + assert(prologue.gpr_backup_offset == total_stack); + total_stack += 16 * prologue.n_gpr_backup; + } + + // FPRS + if (prologue.n_fpr_backup) { + total_stack = align4(total_stack); + assert(prologue.fpr_backup_offset == total_stack); + total_stack += 4 * prologue.n_fpr_backup; + } + + total_stack = align16(total_stack); + + // End! + assert(prologue.total_stack_usage == total_stack); + } + + // it's fine to have the entire first basic block be the prologue - you could loop back to the + // first instruction past the prologue. + assert(basic_blocks.at(0).end_word >= prologue_end); + basic_blocks.at(0).start_word = prologue_end; + prologue.decoded = true; + + check_epilogue(file); +} + +/*! + * Print info about the prologue and stack. + */ +std::string Function::Prologue::to_string(int indent) const { + char buff[512]; + char* buff_ptr = buff; + std::string indent_str(indent, ' '); + if (!decoded) { + return indent_str + ";BAD PROLOGUE"; + } + buff_ptr += sprintf(buff_ptr, "%s;stack: total 0x%02x, fp? %d ra? %d ep? %d", indent_str.c_str(), + total_stack_usage, fp_set, ra_backed_up, epilogue_ok); + if (n_stack_var_bytes) { + buff_ptr += sprintf(buff_ptr, "\n%s;stack_vars: %d bytes at %d", indent_str.c_str(), + n_stack_var_bytes, stack_var_offset); + } + if (n_gpr_backup) { + buff_ptr += sprintf(buff_ptr, "\n%s;gprs:", indent_str.c_str()); + for (int i = 0; i < n_gpr_backup; i++) { + buff_ptr += sprintf(buff_ptr, " %s", gpr_backups.at(i).to_string().c_str()); + } + } + if (n_fpr_backup) { + buff_ptr += sprintf(buff_ptr, "\n%s;fprs:", indent_str.c_str()); + for (int i = 0; i < n_fpr_backup; i++) { + buff_ptr += sprintf(buff_ptr, " %s", fpr_backups.at(i).to_string().c_str()); + } + } + return {buff}; +} + +/*! + * Check that the epilogue matches the prologue. + */ +void Function::check_epilogue(const LinkedObjectFile& file) { + (void)file; + if (!prologue.decoded || suspected_asm) { + printf("not decoded, or suspected asm, skipping epilogue\n"); + return; + } + + // start at the end and move up. + int idx = int(instructions.size()) - 1; + + // seek past alignment nops + while (is_nop(instructions.at(idx))) { + idx--; + } + + epilogue_end = idx; + // stack restore + if (prologue.total_stack_usage) { + // hack - sometimes an asm function has a compiler inserted jr ra/daddu sp sp r0 that follows + // the "true" return. We really should have this function flagged as asm, but for now, we can + // simply skip over the compiler-generated jr ra/daddu sp sp r0. + if (is_gpr_3(instructions.at(idx), InstructionKind::DADDU, make_gpr(Reg::SP), make_gpr(Reg::SP), + make_gpr(Reg::R0))) { + idx--; + assert(is_jr_ra(instructions.at(idx))); + idx--; + printf( + "[Warning] %s Double Return Epilogue Hack! This is probably an ASM function in " + "disguise\n", + guessed_name.to_string().c_str()); + warnings += "Double Return Epilogue - this is probably an ASM function\n"; + } + // delay slot should be daddiu sp, sp, offset + assert(is_gpr_2_imm_int(instructions.at(idx), InstructionKind::DADDIU, make_gpr(Reg::SP), + make_gpr(Reg::SP), prologue.total_stack_usage)); + idx--; + } else { + // delay slot is always daddu sp, sp, r0... + assert(is_gpr_3(instructions.at(idx), InstructionKind::DADDU, make_gpr(Reg::SP), + make_gpr(Reg::SP), make_gpr(Reg::R0))); + idx--; + } + + // jr ra + assert(is_jr_ra(instructions.at(idx))); + idx--; + + // restore gprs + for (int i = 0; i < prologue.n_gpr_backup; i++) { + int gpr_idx = prologue.n_gpr_backup - (1 + i); + const auto& expected_reg = gpr_backups.at(gpr_idx); + auto expected_offset = prologue.gpr_backup_offset + 16 * i; + assert(is_no_ll_gpr_load(instructions.at(idx), 16, true, expected_reg, expected_offset, + make_gpr(Reg::SP))); + idx--; + } + + // restore fprs + for (int i = 0; i < prologue.n_fpr_backup; i++) { + int fpr_idx = prologue.n_fpr_backup - (1 + i); + const auto& expected_reg = fpr_backups.at(fpr_idx); + auto expected_offset = prologue.fpr_backup_offset + 4 * i; + assert( + is_no_ll_fpr_load(instructions.at(idx), expected_reg, expected_offset, make_gpr(Reg::SP))); + idx--; + } + + // restore fp + if (prologue.fp_backed_up) { + assert(is_no_ll_gpr_load(instructions.at(idx), 8, true, make_gpr(Reg::FP), + prologue.fp_backup_offset, make_gpr(Reg::SP))); + idx--; + } + + // restore ra + if (prologue.ra_backed_up) { + assert(is_no_ll_gpr_load(instructions.at(idx), 8, true, make_gpr(Reg::RA), + prologue.ra_backup_offset, make_gpr(Reg::SP))); + idx--; + } + + assert(!basic_blocks.empty()); + assert(idx + 1 >= basic_blocks.back().start_word); + basic_blocks.back().end_word = idx + 1; + prologue.epilogue_ok = true; + epilogue_start = idx + 1; +} + +/*! + * Look through all blocks in this function for storing the address of a function into a symbol. + * This indicates the stored function address belongs to a global function with the same name as + * the symbol. + * + * Updates the guessed_name of the function and updates type_info + */ +void Function::find_global_function_defs(LinkedObjectFile& file) { + int state = 0; + int label_id = -1; + Register reg; + + for (const auto& instr : instructions) { + // look for LUIs always + if (instr.kind == InstructionKind::LUI && instr.get_src(0).kind == InstructionAtom::LABEL) { + state = 1; + reg = instr.get_dst(0).get_reg(); + label_id = instr.get_src(0).get_label(); + assert(label_id != -1); + continue; + } + + if (state == 1) { + // Look for ORI + if (instr.kind == InstructionKind::ORI && instr.get_src(0).get_reg() == reg && + instr.get_src(1).get_label() == label_id) { + state = 2; + reg = instr.get_dst(0).get_reg(); + continue; + } else { + state = 0; + } + } + + if (state == 2) { + // Look for SW + if (instr.kind == InstructionKind::SW && instr.get_src(0).get_reg() == reg && + instr.get_src(2).get_reg() == make_gpr(Reg::S7)) { + // done! + std::string name = instr.get_src(1).get_sym(); + if (!file.label_points_to_code(label_id)) { + // printf("discard as not code: %s\n", name.c_str()); + } else { + auto& func = file.get_function_at_label(label_id); + assert(func.guessed_name.empty()); + func.guessed_name.set_as_global(name); + get_type_info().inform_symbol(name, TypeSpec("function")); + // todo - inform function. + } + + } else { + state = 0; + } + } + } +} + +/*! + * Look through this function to find calls to method-set! which define methods. + * Updates the guessed_name of the function and updates type_info. + */ +void Function::find_method_defs(LinkedObjectFile& file) { + int state = 0; + int label_id = -1; + int method_id = -1; + Register lui_reg; + std::string type_name; + + for (const auto& instr : instructions) { + // look for lw t9, method-set!(s7) + if (instr.kind == InstructionKind::LW && instr.get_dst(0).get_reg() == make_gpr(Reg::T9) && + instr.get_src(0).kind == InstructionAtom::IMM_SYM && + instr.get_src(0).get_sym() == "method-set!" && + instr.get_src(1).get_reg() == make_gpr(Reg::S7)) { + state = 1; + continue; + } + + if (state == 1) { + // look for lw a0, type-name(s7) + if (instr.kind == InstructionKind::LW && instr.get_dst(0).get_reg() == make_gpr(Reg::A0) && + instr.get_src(0).kind == InstructionAtom::IMM_SYM && + instr.get_src(1).get_reg() == make_gpr(Reg::S7)) { + type_name = instr.get_src(0).get_sym(); + state = 2; + continue; + } else { + state = 0; + } + } + + if (state == 2) { + // look for addiu a1, r0, x + if (instr.kind == InstructionKind::ADDIU && instr.get_dst(0).get_reg() == make_gpr(Reg::A1) && + instr.get_src(0).get_reg() == make_gpr(Reg::R0)) { + method_id = instr.get_src(1).get_imm(); + state = 3; + continue; + } else { + state = 0; + } + } + + if (state == 3) { + // look for lui + if (instr.kind == InstructionKind::LUI && instr.get_src(0).kind == InstructionAtom::LABEL) { + state = 4; + lui_reg = instr.get_dst(0).get_reg(); + label_id = instr.get_src(0).get_label(); + assert(label_id != -1); + continue; + } else { + state = 0; + } + } + + if (state == 4) { + if (instr.kind == InstructionKind::ORI && instr.get_src(0).get_reg() == lui_reg && + instr.get_src(1).get_label() == label_id) { + state = 5; + lui_reg = instr.get_dst(0).get_reg(); + continue; + } else { + state = 0; + } + } + + if (state == 5) { + if (instr.kind == InstructionKind::JALR && instr.get_dst(0).get_reg() == make_gpr(Reg::RA) && + instr.get_src(0).get_reg() == make_gpr(Reg::T9)) { + auto& func = file.get_function_at_label(label_id); + assert(func.guessed_name.empty()); + func.guessed_name.set_as_method(type_name, method_id); + state = 0; + continue; + } + } + } +} \ No newline at end of file diff --git a/decompiler/Function/Function.h b/decompiler/Function/Function.h new file mode 100644 index 000000000..d8bfeba20 --- /dev/null +++ b/decompiler/Function/Function.h @@ -0,0 +1,122 @@ +#ifndef NEXT_FUNCTION_H +#define NEXT_FUNCTION_H + +#include +#include +#include "decompiler/Disasm/Instruction.h" +#include "BasicBlocks.h" +#include "CfgVtx.h" + +struct FunctionName { + enum class FunctionKind { + UNIDENTIFIED, // hasn't been identified yet. + GLOBAL, // global named function + METHOD, + TOP_LEVEL_INIT, + } kind = FunctionKind::UNIDENTIFIED; + + std::string function_name; // only applicable for GLOBAL + std::string type_name; // only applicable for METHOD + int method_id = -1; // only applicable for METHOD + + std::string to_string() const { + switch(kind) { + case FunctionKind::GLOBAL: + return function_name; + case FunctionKind::METHOD: + return "(method " + std::to_string(method_id) + " " + type_name + ")"; + case FunctionKind::TOP_LEVEL_INIT: + return "(top-level-login)"; + case FunctionKind::UNIDENTIFIED: + return "(?)"; + default: + assert(false); + } + } + + bool empty() const { + return kind == FunctionKind::UNIDENTIFIED; + } + + void set_as_top_level() { + kind = FunctionKind::TOP_LEVEL_INIT; + } + + void set_as_global(std::string name) { + kind = FunctionKind::GLOBAL; + function_name = std::move(name); + } + + void set_as_method(std::string tn, int id) { + kind = FunctionKind::METHOD; + type_name = std::move(tn); + method_id = id; + } + + bool expected_unique() const { + return kind == FunctionKind::GLOBAL || kind == FunctionKind::METHOD; + } +}; + +class Function { + public: + Function(int _start_word, int _end_word); + void analyze_prologue(const LinkedObjectFile& file); + void find_global_function_defs(LinkedObjectFile& file); + void find_method_defs(LinkedObjectFile& file); + + int segment = -1; + int start_word = -1; + int end_word = -1; // not inclusive, but does include padding. + + FunctionName guessed_name; + + bool suspected_asm = false; + + std::vector instructions; + std::vector basic_blocks; + std::shared_ptr cfg = nullptr; + + int prologue_start = -1; + int prologue_end = -1; + + int epilogue_start = -1; + int epilogue_end = -1; + + std::string warnings; + + struct Prologue { + bool decoded = false; // have we removed the prologue from basic blocks? + int total_stack_usage = -1; + + // ra/fp are treated differently from other register backups + bool ra_backed_up = false; + int ra_backup_offset = -1; + + bool fp_backed_up = false; + int fp_backup_offset = -1; + + bool fp_set = false; + + int n_gpr_backup = 0; + int gpr_backup_offset = -1; + + int n_fpr_backup = 0; + int fpr_backup_offset = -1; + + int n_stack_var_bytes = 0; + int stack_var_offset = -1; + + bool epilogue_ok = false; + + std::string to_string(int indent = 0) const; + + } prologue; + + bool uses_fp_register = false; + + private: + void check_epilogue(const LinkedObjectFile& file); +}; + +#endif // NEXT_FUNCTION_H diff --git a/decompiler/ObjectFile/LinkedObjectFile.cpp b/decompiler/ObjectFile/LinkedObjectFile.cpp new file mode 100644 index 000000000..d02fc3bf3 --- /dev/null +++ b/decompiler/ObjectFile/LinkedObjectFile.cpp @@ -0,0 +1,853 @@ +/*! + * @file LinkedObjectFile.cpp + * An object file's data with linking information included. + */ +#include "LinkedObjectFile.h" +#include +#include +#include +#include +#include "decompiler/Disasm/InstructionDecode.h" +#include "decompiler/config.h" + +/*! + * Set the number of segments in this object file. + * This can only be done once, and must be done before adding any words. + */ +void LinkedObjectFile::set_segment_count(int n_segs) { + assert(segments == 0); + segments = n_segs; + words_by_seg.resize(n_segs); + label_per_seg_by_offset.resize(n_segs); + offset_of_data_zone_by_seg.resize(n_segs); + functions_by_seg.resize(n_segs); +} + +/*! + * Add a single word to the given segment. + */ +void LinkedObjectFile::push_back_word_to_segment(uint32_t word, int segment) { + words_by_seg.at(segment).emplace_back(word); +} + +/*! + * Get a label ID for a label which points to the given offset in the given segment. + * Will return an existing label if one exists. + */ +int LinkedObjectFile::get_label_id_for(int seg, int offset) { + auto kv = label_per_seg_by_offset.at(seg).find(offset); + if (kv == label_per_seg_by_offset.at(seg).end()) { + // create a new label + int id = labels.size(); + Label label; + label.target_segment = seg; + label.offset = offset; + label.name = "L" + std::to_string(id); + label_per_seg_by_offset.at(seg)[offset] = id; + labels.push_back(label); + return id; + } else { + // return an existing label + auto& label = labels.at(kv->second); + assert(label.offset == offset); + assert(label.target_segment == seg); + return kv->second; + } +} + +/*! + * Get the ID of the label which points to the given offset in the given segment. + * Returns -1 if there is no label. + */ +int LinkedObjectFile::get_label_at(int seg, int offset) const { + auto kv = label_per_seg_by_offset.at(seg).find(offset); + if (kv == label_per_seg_by_offset.at(seg).end()) { + return -1; + } + + return kv->second; +} + +/*! + * Does this label point to code? Can point to the middle of a function, or the start of a function. + */ +bool LinkedObjectFile::label_points_to_code(int label_id) const { + auto& label = labels.at(label_id); + auto data_start = int(offset_of_data_zone_by_seg.at(label.target_segment)) * 4; + return label.offset < data_start; +} + +/*! + * Get the function starting at this label, or error if there is none. + */ +Function& LinkedObjectFile::get_function_at_label(int label_id) { + auto& label = labels.at(label_id); + for (auto& func : functions_by_seg.at(label.target_segment)) { + // + 4 to skip past type tag to the first word, which is were the label points. + if (func.start_word * 4 + 4 == label.offset) { + return func; + } + } + + assert(false); + return functions_by_seg.front().front(); // to avoid error +} + +/*! + * Get the name of the label. + */ +std::string LinkedObjectFile::get_label_name(int label_id) const { + return labels.at(label_id).name; +} + +/*! + * Add link information that a word is a pointer to another word. + */ +bool LinkedObjectFile::pointer_link_word(int source_segment, + int source_offset, + int dest_segment, + int dest_offset) { + assert((source_offset % 4) == 0); + + auto& word = words_by_seg.at(source_segment).at(source_offset / 4); + assert(word.kind == LinkedWord::PLAIN_DATA); + + if (dest_offset / 4 > (int)words_by_seg.at(dest_segment).size()) { + // printf("HACK bad link ignored!\n"); + return false; + } + assert(dest_offset / 4 <= (int)words_by_seg.at(dest_segment).size()); + + word.kind = LinkedWord::PTR; + word.label_id = get_label_id_for(dest_segment, dest_offset); + return true; +} + +/*! + * Add link information that a word is linked to a symbol/type/empty list. + */ +void LinkedObjectFile::symbol_link_word(int source_segment, + int source_offset, + const char* name, + LinkedWord::Kind kind) { + assert((source_offset % 4) == 0); + auto& word = words_by_seg.at(source_segment).at(source_offset / 4); + // assert(word.kind == LinkedWord::PLAIN_DATA); + if (word.kind != LinkedWord::PLAIN_DATA) { + printf("bad symbol link word\n"); + } + word.kind = kind; + word.symbol_name = name; +} + +/*! + * Add link information that a word's lower 16 bits are the offset of the given symbol relative to + * the symbol table register. + */ +void LinkedObjectFile::symbol_link_offset(int source_segment, int source_offset, const char* name) { + assert((source_offset % 4) == 0); + auto& word = words_by_seg.at(source_segment).at(source_offset / 4); + assert(word.kind == LinkedWord::PLAIN_DATA); + word.kind = LinkedWord::SYM_OFFSET; + word.symbol_name = name; +} + +/*! + * Add link information that a lui/ori pair will load a pointer. + */ +void LinkedObjectFile::pointer_link_split_word(int source_segment, + int source_hi_offset, + int source_lo_offset, + int dest_segment, + int dest_offset) { + assert((source_hi_offset % 4) == 0); + assert((source_lo_offset % 4) == 0); + + auto& hi_word = words_by_seg.at(source_segment).at(source_hi_offset / 4); + auto& lo_word = words_by_seg.at(source_segment).at(source_lo_offset / 4); + + // assert(dest_offset / 4 <= (int)words_by_seg.at(dest_segment).size()); + assert(hi_word.kind == LinkedWord::PLAIN_DATA); + assert(lo_word.kind == LinkedWord::PLAIN_DATA); + + hi_word.kind = LinkedWord::HI_PTR; + hi_word.label_id = get_label_id_for(dest_segment, dest_offset); + + lo_word.kind = LinkedWord::LO_PTR; + lo_word.label_id = hi_word.label_id; +} + +/*! + * Rename the labels so they are named L1, L2, ..., in the order of the addresses that they refer + * to. Will clear any custom label names. + */ +uint32_t LinkedObjectFile::set_ordered_label_names() { + std::vector indices(labels.size()); + std::iota(indices.begin(), indices.end(), 0); + + std::sort(indices.begin(), indices.end(), [&](int a, int b) { + auto& la = labels.at(a); + auto& lb = labels.at(b); + if (la.target_segment == lb.target_segment) { + return la.offset < lb.offset; + } + return la.target_segment < lb.target_segment; + }); + + for (size_t i = 0; i < indices.size(); i++) { + auto& label = labels.at(indices[i]); + label.name = "L" + std::to_string(i + 1); + } + + return labels.size(); +} + +static const char* segment_names[] = {"main segment", "debug segment", "top-level segment"}; + +/*! + * Print all the words, with link information and labels. + */ +std::string LinkedObjectFile::print_words() { + std::string result; + + assert(segments <= 3); + for (int seg = segments; seg-- > 0;) { + // segment header + result += ";------------------------------------------\n; "; + result += segment_names[seg]; + result += "\n;------------------------------------------\n"; + + // print each word in the segment + for (size_t i = 0; i < words_by_seg.at(seg).size(); i++) { + for (int j = 0; j < 4; j++) { + auto label_id = get_label_at(seg, i * 4 + j); + if (label_id != -1) { + result += labels.at(label_id).name + ":"; + if (j != 0) { + result += " (offset " + std::to_string(j) + ")"; + } + result += "\n"; + } + } + + auto& word = words_by_seg[seg][i]; + append_word_to_string(result, word); + } + } + + return result; +} + +/*! + * Add a word's printed representation to the end of a string. Internal helper for print_words. + */ +void LinkedObjectFile::append_word_to_string(std::string& dest, const LinkedWord& word) const { + char buff[128]; + + switch (word.kind) { + case LinkedWord::PLAIN_DATA: + sprintf(buff, " .word 0x%x\n", word.data); + break; + case LinkedWord::PTR: + sprintf(buff, " .word %s\n", labels.at(word.label_id).name.c_str()); + break; + case LinkedWord::SYM_PTR: + sprintf(buff, " .symbol %s\n", word.symbol_name.c_str()); + break; + case LinkedWord::TYPE_PTR: + sprintf(buff, " .type %s\n", word.symbol_name.c_str()); + break; + case LinkedWord::EMPTY_PTR: + sprintf(buff, " .empty-list\n"); // ? + break; + case LinkedWord::HI_PTR: + sprintf(buff, " .ptr-hi 0x%x %s\n", word.data >> 16, + labels.at(word.label_id).name.c_str()); + break; + case LinkedWord::LO_PTR: + sprintf(buff, " .ptr-lo 0x%x %s\n", word.data >> 16, + labels.at(word.label_id).name.c_str()); + break; + case LinkedWord::SYM_OFFSET: + sprintf(buff, " .sym-off 0x%x %s\n", word.data >> 16, word.symbol_name.c_str()); + break; + default: + throw std::runtime_error("nyi"); + } + + dest += buff; +} + +/*! + * For each segment, determine where the data area starts. Before the data area is the code area. + */ +void LinkedObjectFile::find_code() { + if (segments == 1) { + // single segment object files should never have any code. + auto& seg = words_by_seg.front(); + for (auto& word : seg) { + if (!word.symbol_name.empty()) { + assert(word.symbol_name != "function"); + } + } + offset_of_data_zone_by_seg.at(0) = 0; + stats.data_bytes = words_by_seg.front().size() * 4; + stats.code_bytes = 0; + + } else if (segments == 3) { + // V3 object files will have all the functions, then all the static data. So to find the + // divider, we look for the last "function" tag, then find the last jr $ra instruction after + // that (plus one for delay slot) and assume that after that is data. Additionally, we check to + // make sure that there are no "function" type tags in the data section, although this is + // redundant. + for (int i = 0; i < segments; i++) { + // try to find the last reference to "function": + bool found_function = false; + size_t function_loc = -1; + for (size_t j = words_by_seg.at(i).size(); j-- > 0;) { + auto& word = words_by_seg.at(i).at(j); + if (word.kind == LinkedWord::TYPE_PTR && word.symbol_name == "function") { + function_loc = j; + found_function = true; + break; + } + } + + if (found_function) { + // look forward until we find "jr ra" + const uint32_t jr_ra = 0x3e00008; + bool found_jr_ra = false; + size_t jr_ra_loc = -1; + + for (size_t j = function_loc; j < words_by_seg.at(i).size(); j++) { + auto& word = words_by_seg.at(i).at(j); + if (word.kind == LinkedWord::PLAIN_DATA && word.data == jr_ra) { + found_jr_ra = true; + jr_ra_loc = j; + } + } + + assert(found_jr_ra); + assert(jr_ra_loc + 1 < words_by_seg.at(i).size()); + offset_of_data_zone_by_seg.at(i) = jr_ra_loc + 2; + + } else { + // no functions + offset_of_data_zone_by_seg.at(i) = 0; + } + + // add label for debug purposes + if (offset_of_data_zone_by_seg.at(i) < words_by_seg.at(i).size()) { + auto data_label_id = get_label_id_for(i, 4 * (offset_of_data_zone_by_seg.at(i))); + labels.at(data_label_id).name = "L-data-start"; + } + + // verify there are no functions after the data section starts + for (size_t j = offset_of_data_zone_by_seg.at(i); j < words_by_seg.at(i).size(); j++) { + auto& word = words_by_seg.at(i).at(j); + if (word.kind == LinkedWord::TYPE_PTR && word.symbol_name == "function") { + assert(false); + } + } + + // sizes: + stats.data_bytes += 4 * (words_by_seg.at(i).size() - offset_of_data_zone_by_seg.at(i)) * 4; + stats.code_bytes += 4 * offset_of_data_zone_by_seg.at(i); + } + } else { + // for files which we couldn't extract link data yet, they will have 0 segments and its ok. + assert(segments == 0); + } +} + +/*! + * Find all the functions in each segment. + */ +void LinkedObjectFile::find_functions() { + if (segments == 1) { + // it's a v2 file, shouldn't have any functions + assert(offset_of_data_zone_by_seg.at(0) == 0); + } else { + // we assume functions don't have any data in between them, so we use the "function" type tag to + // mark the end of the previous function and the start of the next. This means that some + // functions will have a few 0x0 words after then for padding (GOAL functions are aligned), but + // this is something that the disassembler should handle. + for (int seg = 0; seg < segments; seg++) { + // start at the end and work backward... + int function_end = offset_of_data_zone_by_seg.at(seg); + while (function_end > 0) { + // back up until we find function type tag + int function_tag_loc = function_end; + bool found_function_tag_loc = false; + for (; function_tag_loc-- > 0;) { + auto& word = words_by_seg.at(seg).at(function_tag_loc); + if (word.kind == LinkedWord::TYPE_PTR && word.symbol_name == "function") { + found_function_tag_loc = true; + break; + } + } + + // mark this as a function, and try again from the current function start + assert(found_function_tag_loc); + stats.function_count++; + functions_by_seg.at(seg).emplace_back(function_tag_loc, function_end); + function_end = function_tag_loc; + } + + std::reverse(functions_by_seg.at(seg).begin(), functions_by_seg.at(seg).end()); + } + } +} + +/*! + * Run the disassembler on all functions. + */ +void LinkedObjectFile::disassemble_functions() { + for (int seg = 0; seg < segments; seg++) { + for (auto& function : functions_by_seg.at(seg)) { + for (auto word = function.start_word; word < function.end_word; word++) { + // decode! + function.instructions.push_back( + decode_instruction(words_by_seg.at(seg).at(word), *this, seg, word)); + if (function.instructions.back().is_valid()) { + stats.decoded_ops++; + } + } + } + } +} + +/*! + * Analyze disassembly for use of the FP register, and add labels for fp-relative data access + */ +void LinkedObjectFile::process_fp_relative_links() { + for (int seg = 0; seg < segments; seg++) { + for (auto& function : functions_by_seg.at(seg)) { + for (size_t instr_idx = 0; instr_idx < function.instructions.size(); instr_idx++) { + // we possibly need to look at three instructions + auto& instr = function.instructions[instr_idx]; + auto* prev_instr = (instr_idx > 0) ? &function.instructions[instr_idx - 1] : nullptr; + auto* pprev_instr = (instr_idx > 1) ? &function.instructions[instr_idx - 2] : nullptr; + + // ignore storing FP onto the stack + if ((instr.kind == InstructionKind::SD || instr.kind == InstructionKind::SQ) && + instr.get_src(0).get_reg() == Register(Reg::GPR, Reg::FP)) { + continue; + } + + // HACKs + if (instr.kind == InstructionKind::PEXTLW) { + continue; + } + + // search over instruction sources + for (int i = 0; i < instr.n_src; i++) { + auto& src = instr.src[i]; + if (src.kind == InstructionAtom::REGISTER // must be reg + && src.get_reg().get_kind() == Reg::GPR // gpr + && src.get_reg().get_gpr() == Reg::FP) { // fp reg. + + stats.n_fp_reg_use++; + + // offset of fp at this instruction. + int current_fp = 4 * (function.start_word + 1); + function.uses_fp_register = true; + + switch (instr.kind) { + // fp-relative load + case InstructionKind::LW: + case InstructionKind::LWC1: + case InstructionKind::LD: + // generate pointer to fp-relative data + case InstructionKind::DADDIU: { + auto& atom = instr.get_imm_src(); + atom.set_label(get_label_id_for(seg, current_fp + atom.get_imm())); + stats.n_fp_reg_use_resolved++; + } break; + + // in the case that addiu doesn't have enough range (+/- 2^15), GOAL has two + // strategies: 1). use ori + daddu (ori doesn't sign extend, so this lets us go +2^16, + // -0) 2). use lui + ori + daddu (can reach anywhere in the address space) It seems + // that addu is used to get pointers to floating point values and daddu is used in + // other cases. Also, the position of the fp register is swapped between the two. + case InstructionKind::DADDU: + case InstructionKind::ADDU: { + assert(prev_instr); + assert(prev_instr->kind == InstructionKind::ORI); + int offset_reg_src_id = instr.kind == InstructionKind::DADDU ? 0 : 1; + auto offset_reg = instr.get_src(offset_reg_src_id).get_reg(); + assert(offset_reg == prev_instr->get_dst(0).get_reg()); + assert(offset_reg == prev_instr->get_src(0).get_reg()); + auto& atom = prev_instr->get_imm_src(); + int additional_offset = 0; + if (pprev_instr && pprev_instr->kind == InstructionKind::LUI) { + assert(pprev_instr->get_dst(0).get_reg() == offset_reg); + additional_offset = (1 << 16) * pprev_instr->get_imm_src().get_imm(); + } + atom.set_label( + get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset)); + stats.n_fp_reg_use_resolved++; + } break; + + default: + printf("unknown fp using op: %s\n", instr.to_string(*this).c_str()); + assert(false); + } + } + } + } + } + } +} + +/*! + * Print disassembled functions and data segments. + */ +std::string LinkedObjectFile::print_disassembly() { + bool write_hex = get_config().write_hex_near_instructions; + std::string result; + + assert(segments <= 3); + for (int seg = segments; seg-- > 0;) { + // segment header + result += ";------------------------------------------\n; "; + result += segment_names[seg]; + result += "\n;------------------------------------------\n\n"; + + // functions + for (auto& func : functions_by_seg.at(seg)) { + result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n"; + result += "; .function " + func.guessed_name.to_string() + "\n"; + result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n"; + result += func.prologue.to_string(2) + "\n"; + if(!func.warnings.empty()) { + result += "Warnings: " + func.warnings + "\n"; + } + + // print each instruction in the function. + bool in_delay_slot = false; + + for (int i = 1; i < func.end_word - func.start_word; i++) { + auto label_id = get_label_at(seg, (func.start_word + i) * 4); + if (label_id != -1) { + result += labels.at(label_id).name + ":\n"; + } + + for (int j = 1; j < 4; j++) { + // assert(get_label_at(seg, (func.start_word + i)*4 + j) == -1); + if (get_label_at(seg, (func.start_word + i) * 4 + j) != -1) { + result += "BAD OFFSET LABEL: "; + result += labels.at(get_label_at(seg, (func.start_word + i) * 4 + j)).name + "\n"; + assert(false); + } + } + + auto& instr = func.instructions.at(i); + std::string line = " " + instr.to_string(*this); + + if (write_hex) { + if (line.length() < 60) { + line.append(60 - line.length(), ' '); + } + result += line; + result += " ;;"; + auto& word = words_by_seg[seg].at(func.start_word + i); + append_word_to_string(result, word); + } else { + result += line + "\n"; + } + + if (in_delay_slot) { + result += "\n"; + in_delay_slot = false; + } + + if (gOpcodeInfo[(int)instr.kind].has_delay_slot) { + in_delay_slot = true; + } + } + result += "\n"; + // + // int bid = 0; + // for(auto& bblock : func.basic_blocks) { + // result += "BLOCK " + std::to_string(bid++)+ "\n"; + // for(int i = bblock.start_word; i < bblock.end_word; i++) { + // if(i >= 0 && i < func.instructions.size()) { + // result += func.instructions.at(i).to_string(*this) + "\n"; + // } else { + // result += "BAD BBLOCK INSTR ID " + std::to_string(i); + // } + // } + // } + + // hack + if(func.cfg && !func.cfg->is_fully_resolved()) { + result += func.cfg->to_dot(); + result += "\n"; + } + if(func.cfg) { + result += func.cfg->to_form_string() + "\n"; + + // To debug block stuff. + /* + int bid = 0; + for(auto& block : func.basic_blocks) { + in_delay_slot = false; + result += "B" + std::to_string(bid++) + "\n"; + for(auto i = block.start_word; i < block.end_word; i++) { + auto label_id = get_label_at(seg, (func.start_word + i) * 4); + if (label_id != -1) { + result += labels.at(label_id).name + ":\n"; + } + auto& instr = func.instructions.at(i); + result += " " + instr.to_string(*this) + "\n"; + if (in_delay_slot) { + result += "\n"; + in_delay_slot = false; + } + + if (gOpcodeInfo[(int)instr.kind].has_delay_slot) { + in_delay_slot = true; + } + } + } + */ + } + + + result += "\n\n\n"; + } + + // print data + for (size_t i = offset_of_data_zone_by_seg.at(seg); i < words_by_seg.at(seg).size(); i++) { + for (int j = 0; j < 4; j++) { + auto label_id = get_label_at(seg, i * 4 + j); + if (label_id != -1) { + result += labels.at(label_id).name + ":"; + if (j != 0) { + result += " (offset " + std::to_string(j) + ")"; + } + result += "\n"; + } + } + + auto& word = words_by_seg[seg][i]; + append_word_to_string(result, word); + + if (word.kind == LinkedWord::TYPE_PTR && word.symbol_name == "string") { + result += "; " + get_goal_string(seg, i) + "\n"; + + } + } + } + + return result; +} + +/*! + * Hacky way to get a GOAL string object + */ +std::string LinkedObjectFile::get_goal_string(int seg, int word_idx) { + std::string result = "\""; + // next should be the size + if (word_idx + 1 >= int(words_by_seg[seg].size())) { + return "invalid string!\n"; + } + LinkedWord& size_word = words_by_seg[seg].at(word_idx + 1); + if (size_word.kind != LinkedWord::PLAIN_DATA) { + // sometimes an array of string pointer triggers this! + return "invalid string!\n"; + } + + // result += "(size " + std::to_string(size_word.data) + "): "; + // now characters... + for (size_t i = 0; i < size_word.data; i++) { + int word_offset = word_idx + 2 + (i / 4); + int byte_offset = i % 4; + auto& word = words_by_seg[seg].at(word_offset); + if (word.kind != LinkedWord::PLAIN_DATA) { + return "invalid string! (check me!)\n"; + } + char cword[4]; + memcpy(cword, &word.data, 4); + result += cword[byte_offset]; + } + return result + "\""; +} + +/*! + * Return true if the object file contains any functions at all. + */ +bool LinkedObjectFile::has_any_functions() { + for (auto& fv : functions_by_seg) { + if (!fv.empty()) + return true; + } + return false; +} + +/*! + * Print all scripts in this file. + */ +std::string LinkedObjectFile::print_scripts() { + std::string result; + for (int seg = 0; seg < segments; seg++) { + std::vector already_printed(words_by_seg[seg].size(), false); + + // the linked list layout algorithm of GOAL puts the first pair first. + // so we want to go in forward order to catch the beginning correctly + for (size_t word_idx = 0; word_idx < words_by_seg[seg].size(); word_idx++) { + // don't print parts of scripts we've already seen + // (note that scripts could share contents, which is supported, this is just for starting + // off a script print) + if (already_printed[word_idx]) + continue; + + // check for linked list by looking for anything that accesses this as a pair (offset of 2) + auto label_id = get_label_at(seg, 4 * word_idx + 2); + if (label_id != -1) { + auto& label = labels.at(label_id); + if ((label.offset & 7) == 2) { + result += to_form_script(seg, word_idx, already_printed)->toStringPretty(0, 100) + "\n"; + } + } + } + } + return result; +} + +/*! + * Is the object pointed to the empty list? + */ +bool LinkedObjectFile::is_empty_list(int seg, int byte_idx) { + assert((byte_idx % 4) == 0); + auto& word = words_by_seg.at(seg).at(byte_idx / 4); + return word.kind == LinkedWord::EMPTY_PTR; +} + +/*! + * Convert a linked list to a Form for easy printing. + * Note : this takes the address of the car of the pair. which is perhaps a bit confusing + * (in GOAL, this would be (&-> obj car)) + */ +std::shared_ptr LinkedObjectFile::to_form_script(int seg, + int word_idx, + std::vector& seen) { + // the object to currently print. to start off, create pair from the car address we've been given. + int goal_print_obj = word_idx * 4 + 2; + + // resulting form. we can't have a totally empty list (as an empty list looks like a symbol, + // so it wouldn't be flagged), so it's safe to make this a pair. + auto result = std::make_shared(); + result->kind = FormKind::PAIR; + + // the current pair to fill out. + auto fill = result; + + // loop until we run out of things to add + for (;;) { + // check the thing to print is a a pair. + if ((goal_print_obj & 7) == 2) { + // first convert the car (again, with (&-> obj car)) + fill->pair[0] = to_form_script_object(seg, goal_print_obj - 2, seen); + seen.at(goal_print_obj / 4) = true; + + auto cdr_addr = goal_print_obj + 2; + + if (is_empty_list(seg, cdr_addr)) { + // the list has ended! + fill->pair[1] = gSymbolTable.getEmptyPair(); + return result; + } else { + // cdr object should be aligned. + assert((cdr_addr % 4) == 0); + auto& cdr_word = words_by_seg.at(seg).at(cdr_addr / 4); + // check for proper list + if (cdr_word.kind == LinkedWord::PTR && (labels.at(cdr_word.label_id).offset & 7) == 2) { + // yes, proper list. add another pair and link it in to the list. + goal_print_obj = labels.at(cdr_word.label_id).offset; + fill->pair[1] = std::make_shared(); + fill->pair[1]->kind = FormKind::PAIR; + fill = fill->pair[1]; + } else { + // improper list, put the last thing in and end + fill->pair[1] = to_form_script_object(seg, cdr_addr, seen); + return result; + } + } + } else { + // improper list, should be impossible to get here because of earlier checks + assert(false); + } + } + + return result; +} + +/*! + * Is the thing pointed to a string? + */ +bool LinkedObjectFile::is_string(int seg, int byte_idx) { + if (byte_idx % 4) { + return false; // must be aligned pointer. + } + int type_tag_ptr = byte_idx - 4; + // must fit in segment + if (type_tag_ptr < 0 || size_t(type_tag_ptr) >= words_by_seg.at(seg).size() * 4) { + return false; + } + auto& type_word = words_by_seg.at(seg).at(type_tag_ptr / 4); + return type_word.kind == LinkedWord::TYPE_PTR && type_word.symbol_name == "string"; +} + +/*! + * Convert a (pointer object) to some nice representation. + */ +std::shared_ptr LinkedObjectFile::to_form_script_object(int seg, + int byte_idx, + std::vector& seen) { + std::shared_ptr result; + + switch (byte_idx & 7) { + case 0: + case 4: { + auto& word = words_by_seg.at(seg).at(byte_idx / 4); + if (word.kind == LinkedWord::SYM_PTR) { + // .symbol xxxx + result = toForm(word.symbol_name); + } else if (word.kind == LinkedWord::PLAIN_DATA) { + // .word xxxxx + result = toForm(std::to_string(word.data)); + } else if (word.kind == LinkedWord::PTR) { + // might be a sub-list, or some other random pointer + auto offset = labels.at(word.label_id).offset; + if ((offset & 7) == 2) { + // list! + result = to_form_script(seg, offset / 4, seen); + } else { + if (is_string(seg, offset)) { + result = toForm(get_goal_string(seg, offset / 4 - 1)); + } else { + // some random pointer, just print the label. + result = toForm(labels.at(word.label_id).name); + } + } + } else if (word.kind == LinkedWord::EMPTY_PTR) { + result = gSymbolTable.getEmptyPair(); + } else { + std::string debug; + append_word_to_string(debug, word); + printf("don't know how to print %s\n", debug.c_str()); + assert(false); + } + } break; + + case 2: // bad, a pair snuck through. + default: + // pointers should be aligned! + printf("align %d\n", byte_idx & 7); + assert(false); + } + + return result; +} \ No newline at end of file diff --git a/decompiler/ObjectFile/LinkedObjectFile.h b/decompiler/ObjectFile/LinkedObjectFile.h new file mode 100644 index 000000000..4b87dbe34 --- /dev/null +++ b/decompiler/ObjectFile/LinkedObjectFile.h @@ -0,0 +1,131 @@ +/*! + * @file LinkedObjectFile.h + * An object file's data with linking information included. + */ + +#ifndef NEXT_LINKEDOBJECTFILE_H +#define NEXT_LINKEDOBJECTFILE_H + +#include +#include +#include +#include +#include +#include "LinkedWord.h" +#include "decompiler/Function/Function.h" +#include "decompiler/util/LispPrint.h" + + +/*! + * A label to a location in this object file. + * Doesn't have to be word aligned. + */ +struct Label { + std::string name; + int target_segment; + int offset; // in bytes +}; + +/*! + * An object file's data with linking information included. + */ +class LinkedObjectFile { +public: + LinkedObjectFile() = default; + void set_segment_count(int n_segs); + void push_back_word_to_segment(uint32_t word, int segment); + int get_label_id_for(int seg, int offset); + int get_label_at(int seg, int offset) const; + bool label_points_to_code(int label_id) const; + bool pointer_link_word(int source_segment, int source_offset, int dest_segment, int dest_offset); + void pointer_link_split_word(int source_segment, int source_hi_offset, int source_lo_offset, int dest_segment, int dest_offset); + void symbol_link_word(int source_segment, int source_offset, const char* name, LinkedWord::Kind kind); + void symbol_link_offset(int source_segment, int source_offset, const char* name); + Function& get_function_at_label(int label_id); + std::string get_label_name(int label_id) const; + uint32_t set_ordered_label_names(); + void find_code(); + std::string print_words(); + void find_functions(); + void disassemble_functions(); + void process_fp_relative_links(); + std::string print_scripts(); + std::string print_disassembly(); + bool has_any_functions(); + void append_word_to_string(std::string& dest, const LinkedWord& word) const; + + struct Stats { + uint32_t total_code_bytes = 0; + uint32_t total_v2_code_bytes = 0; + uint32_t total_v2_pointers = 0; + uint32_t total_v2_pointer_seeks = 0; + uint32_t total_v2_link_bytes = 0; + uint32_t total_v2_symbol_links = 0; + uint32_t total_v2_symbol_count = 0; + + uint32_t v3_code_bytes = 0; + uint32_t v3_pointers = 0; + uint32_t v3_split_pointers = 0; + uint32_t v3_word_pointers = 0; + uint32_t v3_pointer_seeks = 0; + uint32_t v3_link_bytes = 0; + + uint32_t v3_symbol_count = 0; + uint32_t v3_symbol_link_offset = 0; + uint32_t v3_symbol_link_word = 0; + + uint32_t data_bytes = 0; + uint32_t code_bytes = 0; + + uint32_t function_count = 0; + uint32_t decoded_ops = 0; + + uint32_t n_fp_reg_use = 0; + uint32_t n_fp_reg_use_resolved = 0; + + + void add(const Stats& other) { + total_code_bytes += other.total_code_bytes; + total_v2_code_bytes += other.total_v2_code_bytes; + total_v2_pointers += other.total_v2_pointers; + total_v2_pointer_seeks += other.total_v2_pointer_seeks; + total_v2_link_bytes += other.total_v2_link_bytes; + total_v2_symbol_links += other.total_v2_symbol_links; + total_v2_symbol_count += other.total_v2_symbol_count; + v3_code_bytes += other.v3_code_bytes; + v3_pointers += other.v3_pointers; + v3_pointer_seeks += other.v3_pointer_seeks; + v3_link_bytes += other.v3_link_bytes; + v3_word_pointers += other.v3_word_pointers; + v3_split_pointers += other.v3_split_pointers; + v3_symbol_count += other.v3_symbol_count; + v3_symbol_link_offset += other.v3_symbol_link_offset; + v3_symbol_link_word += other.v3_symbol_link_word; + data_bytes += other.data_bytes; + code_bytes += other.code_bytes; + function_count += other.function_count; + decoded_ops += other.decoded_ops; + n_fp_reg_use += other.n_fp_reg_use; + n_fp_reg_use_resolved += other.n_fp_reg_use_resolved; + } + } stats; + + int segments = 0; + std::vector> words_by_seg; + std::vector offset_of_data_zone_by_seg; + std::vector> functions_by_seg; + std::vector