#include #include #include #include #include #include #include #include #include #include #include #include "rabbitizer.hpp" #include "rabbitizer.h" #include "elf.h" #if defined(_WIN32) && !defined(__CYGWIN__) #include #endif /* _WIN32 && !__CYGWIN__ */ #if !defined(_MSC_VER) && !defined(__CYGWIN__) && !defined(_WIN32) #define UNIX_PLATFORM #endif #ifdef UNIX_PLATFORM // TODO: determine if any of those headers are not required #include #include #include // for __cxa_demangle #include // for dladdr #include #include #endif #ifndef FULL_TRACEBACK // Change to non-zero to have full traceback, including names not exported #define FULL_TRACEBACK 0 #endif // set this to 1 when testing a new program, to verify that no false function pointers are found #define INSPECT_FUNCTION_POINTERS 0 #ifndef TRACE #define TRACE 0 #endif #define LABELS_64_BIT 1 #ifndef DUMP_INSTRUCTIONS // Set to non-zero to dump actual disassembly when dumping C code #define DUMP_INSTRUCTIONS 0 #endif #define u32be(x) (uint32_t)(((x & 0xff) << 24) + ((x & 0xff00) << 8) + ((x & 0xff0000) >> 8) + ((uint32_t)(x) >> 24)) #define u16be(x) (uint16_t)(((x & 0xff) << 8) + ((x & 0xff00) >> 8)) #define read_u32_be(buf) (uint32_t)(((buf)[0] << 24) + ((buf)[1] << 16) + ((buf)[2] << 8) + ((buf)[3])) #define UniqueId_cpu_li rabbitizer::InstrId::UniqueId::cpu_USERDEF_00 #define UniqueId_cpu_la rabbitizer::InstrId::UniqueId::cpu_USERDEF_01 using namespace std; struct Edge { uint32_t i; uint8_t function_entry : 1; uint8_t function_exit : 1; uint8_t extern_function : 1; uint8_t function_pointer : 1; }; struct Insn { // base instruction rabbitizer::InstructionCpu instruction; // bool is_global_got_memop; bool no_following_successor; // patching instructions bool patched; // lui pairs uint32_t patched_addr; // immediates are 16 bits wide, but they can be either signed or unsigned // a 32 bits signed member can hold all those possible values int32_t patched_imms; rabbitizer::Registers::Cpu::GprO32 lila_dst_reg; int linked_insn; union { uint32_t linked_value; float linked_float; }; // jumptable instructions uint32_t jtbl_addr; uint32_t num_cases; rabbitizer::Registers::Cpu::GprO32 index_reg; // graph vector successors; vector predecessors; uint64_t b_liveout; uint64_t b_livein; uint64_t f_livein; uint64_t f_liveout; Insn(uint32_t word, uint32_t vram) : instruction(word, vram) { this->is_global_got_memop = false; this->no_following_successor = false; this->patched = false; this->patched_addr = 0; this->patched_imms = 0; this->lila_dst_reg = rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero; this->linked_insn = -1; this->linked_value = 0; this->jtbl_addr = 0; this->num_cases = 0; this->index_reg = rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero; this->b_liveout = 0; this->b_livein = 0; this->f_livein = 0; this->f_liveout = 0; } void patchInstruction(rabbitizer::InstrId::UniqueId instructionId) { this->patched = true; RabbitizerInstruction* innerInstr = this->instruction.getCPtr(); innerInstr->uniqueId = (RabbitizerInstrId)(instructionId); innerInstr->descriptor = &RabbitizerInstrDescriptor_Descriptors[innerInstr->uniqueId]; } void patchAddress(rabbitizer::InstrId::UniqueId instructionId, uint32_t newAddress) { this->patchInstruction(instructionId); this->patched_addr = newAddress; } uint32_t getAddress() const { if (this->patched && this->patched_addr != 0) { return this->patched_addr; } if (this->instruction.hasOperandAlias(rabbitizer::OperandType::cpu_label)) { return this->instruction.getInstrIndexAsVram(); } if (this->instruction.isBranch()) { return this->instruction.getVram() + this->instruction.getBranchOffset(); } assert(!"unreachable code"); } void patchImmediate(int32_t newImmediate) { this->patched = true; this->patched_imms = newImmediate; } int32_t getImmediate() const { if (this->patched) { return this->patched_imms; } return this->instruction.getProcessedImmediate(); } std::string disassemble() const { char buffer[0x1000]; int32_t imm; switch (this->instruction.getUniqueId()) { case UniqueId_cpu_li: imm = this->getImmediate(); if (imm >= 0) { sprintf(buffer, "li %s, 0x%X", RabbitizerRegister_getNameGpr((int)this->lila_dst_reg), imm); } else { sprintf(buffer, "li %s, %i", RabbitizerRegister_getNameGpr((int)this->lila_dst_reg), imm); } return buffer; case UniqueId_cpu_la: sprintf(buffer, "la %s, 0x%X", RabbitizerRegister_getNameGpr((int)this->lila_dst_reg), this->getAddress()); return buffer; default: return this->instruction.disassembleInstruction(0); } } }; struct Function { vector returns; // points to delay slots uint32_t end_addr; // address after end uint32_t nargs; uint32_t nret; bool v0_in; bool referenced_by_function_pointer; }; bool conservative; const uint8_t* text_section; uint32_t text_section_len; uint32_t text_vaddr; const uint8_t* rodata_section; uint32_t rodata_section_len; uint32_t rodata_vaddr; const uint8_t* data_section; uint32_t data_section_len; uint32_t data_vaddr; uint32_t bss_section_len; uint32_t bss_vaddr; vector insns; set label_addresses; vector got_globals; vector got_locals; uint32_t gp_value; uint32_t gp_value_adj; map symbol_names; vector> data_function_pointers; set la_function_pointers; map functions; uint32_t main_addr; uint32_t mcount_addr; uint32_t procedure_table_start; uint32_t procedure_table_len; #define FLAG_NO_MEM 1 #define FLAG_VARARG 2 /** * Struct containing information on external functions that are called using the wrappers in `libc_impl.c`. * * name: function name * params: first char is return type, subsequent chars are argument types. Key to chars used: * - 'v' void * - 'i' signed int (int32_t) * - 'u' unsigned int (uint32_t) * - 'p' pointer (uintptr_t) * - 'f' float * - 'd' double * - 'l' signed long long (int64_t) * - 'j' unsigned long long (uint64_t) * - 't' trampoline * * flags: use defines above */ const struct ExternFunction { const char* name; const char* params; int flags; } extern_functions[] = { { "exit", "vi", 0 }, // override exit from application { "abort", "v", 0 }, { "sbrk", "pi", 0 }, { "malloc", "pu", 0 }, { "calloc", "puu", 0 }, { "realloc", "ppu", 0 }, { "free", "vp", 0 }, { "fscanf", "ipp", FLAG_VARARG }, { "printf", "ip", FLAG_VARARG }, { "sprintf", "ipp", FLAG_VARARG }, { "fprintf", "ipp", FLAG_VARARG }, { "_doprnt", "ippp", 0 }, { "strlen", "up", 0 }, { "open", "ipii", 0 }, { "creat", "ipi", 0 }, { "access", "ipi", 0 }, { "rename", "ipp", 0 }, { "utime", "ipp", 0 }, { "flock", "iii", 0 }, { "chmod", "ipu", 0 }, { "umask", "ii", FLAG_NO_MEM }, { "ecvt", "pdipp", 0 }, { "fcvt", "pdipp", 0 }, { "sqrt", "dd", FLAG_NO_MEM }, { "sqrtf", "ff", FLAG_NO_MEM }, { "atoi", "ip", 0 }, { "atol", "ip", 0 }, { "atof", "dp", 0 }, { "strtol", "ippi", 0 }, { "strtoul", "uppi", 0 }, { "strtoll", "lppi", 0 }, { "strtoull", "jppi", 0 }, { "strtod", "dpp", 0 }, { "strchr", "ppi", 0 }, { "strrchr", "ppi", 0 }, { "strcspn", "upp", 0 }, { "strpbrk", "ppp", 0 }, { "fstat", "iip", 0 }, { "stat", "ipp", 0 }, { "ftruncate", "iii", 0 }, { "truncate", "ipi", 0}, { "bcopy", "vppu", 0 }, { "memcpy", "pppu", 0 }, { "memccpy", "pppiu", 0 }, { "read", "iipu", 0 }, { "write", "iipu", 0 }, { "fopen", "ppp", 0 }, { "freopen", "pppp", 0 }, { "fclose", "ip", 0 }, { "ftell", "ip", 0 }, { "rewind", "vp", 0 }, { "fseek", "ipii", 0 }, { "lseek", "iiii", 0 }, { "fflush", "ip", 0 }, { "fchown", "iiii", 0 }, { "dup", "ii", 0 }, { "dup2", "iii", 0 }, { "pipe", "ip", 0 }, { "perror", "vp", 0 }, { "fdopen", "iip", 0 }, { "memset", "ppiu", 0 }, { "bcmp", "ippu", 0 }, { "memcmp", "ippu", 0 }, { "getpid", "i", FLAG_NO_MEM }, { "getpgrp", "i", 0 }, { "remove", "ip", 0 }, { "unlink", "ip", 0 }, { "close", "ii", 0 }, { "strcmp", "ipp", 0 }, { "strncmp", "ippu", 0 }, { "strcpy", "ppp", 0 }, { "strncpy", "pppu", 0 }, { "strcat", "ppp", 0 }, { "strncat", "pppu", 0 }, { "strtok", "ppp", 0 }, { "strstr", "ppp", 0 }, { "strdup", "pp", 0 }, { "toupper", "ii", FLAG_NO_MEM }, { "tolower", "ii", FLAG_NO_MEM }, { "gethostname", "ipu", 0 }, { "isatty", "ii", 0 }, { "strftime", "upupp", 0 }, { "times", "ip", 0 }, { "clock", "i", FLAG_NO_MEM }, { "ctime", "pp", 0 }, { "localtime", "pp", 0 }, { "setvbuf", "ippiu", 0 }, { "__semgetc", "ip", 0 }, { "__semputc", "iip", 0 }, { "fgetc", "ip", 0 }, { "fgets", "ipip", 0 }, { "__filbuf", "ip", 0 }, { "__flsbuf", "iip", 0 }, { "ungetc", "iip", 0 }, { "gets", "pp", 0 }, { "fread", "upuup", 0 }, { "fwrite", "upuup", 0 }, { "fputs", "ipp", 0 }, { "puts", "ip", 0 }, { "getcwd", "ppu", 0 }, { "time", "ip", 0 }, { "bzero", "vpu", 0 }, { "fp_class_d", "id", FLAG_NO_MEM }, { "ldexp", "ddi", FLAG_NO_MEM }, { "__ll_mul", "lll", FLAG_NO_MEM }, { "__ll_div", "lll", FLAG_NO_MEM }, { "__ll_rem", "ljl", FLAG_NO_MEM }, { "__ll_lshift", "llj", FLAG_NO_MEM }, { "__ll_rshift", "llj", FLAG_NO_MEM }, { "__ull_div", "jjj", FLAG_NO_MEM }, { "__ull_rem", "jjj", FLAG_NO_MEM }, { "__ull_rshift", "jjj", FLAG_NO_MEM }, { "__d_to_ull", "jd", FLAG_NO_MEM }, { "__d_to_ll", "ld", FLAG_NO_MEM }, { "__f_to_ull", "jf", FLAG_NO_MEM }, { "__f_to_ll", "lf", FLAG_NO_MEM }, { "__ull_to_f", "fj", FLAG_NO_MEM }, { "__ll_to_f", "fl", FLAG_NO_MEM }, { "__ull_to_d", "dj", FLAG_NO_MEM }, { "__ll_to_d", "dl", FLAG_NO_MEM }, { "_exit", "vi", 0 }, { "_cleanup", "v", 0 }, { "_rld_new_interface", "pu", FLAG_VARARG }, { "_exithandle", "v", 0 }, { "_prctl", "ii", FLAG_VARARG }, { "_atod", "dpii", 0 }, { "pathconf", "ipi", 0 }, { "getenv", "pp", 0 }, { "gettxt", "ppp", 0 }, { "setlocale", "pip", 0 }, { "mmap", "ppuiiii", 0 }, { "munmap", "ipu", 0 }, { "mprotect", "ipui", 0 }, { "sysconf", "ii", 0 }, { "getpagesize", "i", 0 }, { "strerror", "pi", 0 }, { "ioctl", "iiu", FLAG_VARARG }, { "fcntl", "iii", FLAG_VARARG }, { "signal", "pit", 0 }, { "sigset", "pit", 0 }, { "get_fpc_csr", "i", 0 }, { "set_fpc_csr", "ii", 0 }, { "setjmp", "ip", 0 }, { "longjmp", "vpi", 0 }, { "tempnam", "ppp", 0 }, { "tmpnam", "pp", 0 }, { "mktemp", "pp", 0 }, { "mkstemp", "ip", 0 }, { "tmpfile", "p", 0 }, { "wait", "ip", 0 }, { "kill", "iii", 0 }, { "execlp", "ip", FLAG_VARARG }, { "execv", "ipp", 0 }, { "execvp", "ipp", 0 }, { "fork", "i", 0 }, { "system", "ip", 0 }, { "tsearch", "pppp", 0 }, { "tfind", "pppp", 0 }, { "qsort", "vpuut", 0 }, { "regcmp", "pp", FLAG_VARARG }, { "regex", "ppp", FLAG_VARARG }, { "__assert", "vppi", 0 }, }; void disassemble(void) { uint32_t i; RabbitizerConfig_Cfg.misc.omit0XOnSmallImm = true; RabbitizerConfig_Cfg.misc.opcodeLJust -= 8; RabbitizerConfig_Cfg.misc.upperCaseImm = false; insns.reserve(1 + text_section_len / sizeof(uint32_t)); // +1 for dummy instruction for (i = 0; i < text_section_len; i += sizeof(uint32_t)) { uint32_t word = read_u32_be(&text_section[i]); Insn insn(word, text_vaddr + i); insns.push_back(insn); } { // Add dummy NOP instruction to avoid out of bounds Insn insn(0x00000000, text_vaddr + i); insn.no_following_successor = true; insns.push_back(insn); } } void add_function(uint32_t addr) { if (addr >= text_vaddr && addr < text_vaddr + text_section_len) { functions[addr]; } } map::iterator find_function(uint32_t addr) { if (functions.size() == 0) { return functions.end(); } auto it = functions.upper_bound(addr); if (it == functions.begin()) { return functions.end(); } --it; return it; } rabbitizer::Registers::Cpu::GprO32 get_dest_reg(const Insn& insn) { switch (insn.instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_jalr: // jalr technically modifies rd, so an empty case is here to avoid crashing break; case UniqueId_cpu_li: case UniqueId_cpu_la: return insn.lila_dst_reg; default: if (insn.instruction.modifiesRt()) { return insn.instruction.GetO32_rt(); } else if (insn.instruction.modifiesRd()) { return insn.instruction.GetO32_rd(); } break; } return rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero; } // try to find a matching LUI for a given register void link_with_lui(int offset, rabbitizer::Registers::Cpu::GprO32 reg, int mem_imm) { #define MAX_LOOKBACK 128 // don't attempt to compute addresses for zero offset // end search after some sane max number of instructions int end_search = std::max(0, offset - MAX_LOOKBACK); for (int search = offset - 1; search >= end_search; search--) { switch (insns[search].instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_lui: if (reg == insns[search].instruction.GetO32_rt()) { goto loop_end; } continue; case rabbitizer::InstrId::UniqueId::cpu_lw: case rabbitizer::InstrId::UniqueId::cpu_ld: case rabbitizer::InstrId::UniqueId::cpu_addiu: case rabbitizer::InstrId::UniqueId::cpu_add: case rabbitizer::InstrId::UniqueId::cpu_sub: case rabbitizer::InstrId::UniqueId::cpu_subu: if (reg == get_dest_reg(insns[search])) { if ((insns[search].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_lw) && insns[search].instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_gp) { int mem_imm0 = insns[search].instruction.getProcessedImmediate(); uint32_t got_entry = (mem_imm0 + gp_value_adj) / sizeof(uint32_t); if (got_entry < got_locals.size()) { // used for static functions uint32_t addr = got_locals[got_entry] + mem_imm; insns[search].linked_insn = offset; insns[search].linked_value = addr; insns[offset].linked_insn = search; insns[offset].linked_value = addr; // Patch instruction to contain full address insns[search].lila_dst_reg = get_dest_reg(insns[search]); insns[search].patchAddress(UniqueId_cpu_la, addr); // Patch instruction to have offset 0 switch (insns[offset].instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_addiu: { rabbitizer::Registers::Cpu::GprO32 dst_reg = insns[offset].instruction.GetO32_rt(); insns[offset].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_move); // Patch the destination register too insns[offset].instruction.Set_rd(dst_reg); } if (addr >= text_vaddr && addr < text_vaddr + text_section_len) { add_function(addr); } goto loop_end; case rabbitizer::InstrId::UniqueId::cpu_lb: case rabbitizer::InstrId::UniqueId::cpu_lbu: case rabbitizer::InstrId::UniqueId::cpu_sb: case rabbitizer::InstrId::UniqueId::cpu_lh: case rabbitizer::InstrId::UniqueId::cpu_lhu: case rabbitizer::InstrId::UniqueId::cpu_sh: case rabbitizer::InstrId::UniqueId::cpu_lw: case rabbitizer::InstrId::UniqueId::cpu_sw: case rabbitizer::InstrId::UniqueId::cpu_ldc1: case rabbitizer::InstrId::UniqueId::cpu_lwc1: case rabbitizer::InstrId::UniqueId::cpu_swc1: insns[offset].patchImmediate(0); goto loop_end; default: assert(0 && "Unsupported instruction type"); } } goto loop_end; } else { // ignore: reg is pointer, offset is probably struct data member goto loop_end; } } continue; case rabbitizer::InstrId::UniqueId::cpu_jr: if ((insns[search].instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) && (offset - search >= 2)) { // stop looking when previous `jr ra` is hit, // but ignore if `offset` is branch delay slot for this `jr ra` goto loop_end; } continue; default: continue; } } loop_end:; } // for a given `jalr t9`, find the matching t9 load void link_with_jalr(int offset) { // end search after some sane max number of instructions int end_search = std::max(0, offset - MAX_LOOKBACK); for (int search = offset - 1; search >= end_search; search--) { if (get_dest_reg(insns[search]) == rabbitizer::Registers::Cpu::GprO32::GPR_O32_t9) { // should be a switch with returns switch (insns[search].instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_lw: case UniqueId_cpu_la: if (insns[search].is_global_got_memop || (insns[search].instruction.getUniqueId() == UniqueId_cpu_la)) { insns[search].linked_insn = offset; insns[offset].linked_insn = search; insns[offset].linked_value = insns[search].linked_value; insns[offset].patchAddress(rabbitizer::InstrId::UniqueId::cpu_jal, insns[search].linked_value); insns[search].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); insns[search].is_global_got_memop = false; add_function(insns[search].linked_value); } return; case rabbitizer::InstrId::UniqueId::cpu_addiu: if (insns[search].linked_insn != -1) { uint32_t first = insns[search].linked_insn; // not describing as patched since instruction not edited insns[search].linked_insn = offset; insns[offset].linked_insn = first; insns[offset].linked_value = insns[search].linked_value; } return; case rabbitizer::InstrId::UniqueId::cpu_ld: case rabbitizer::InstrId::UniqueId::cpu_addu: case rabbitizer::InstrId::UniqueId::cpu_add: case rabbitizer::InstrId::UniqueId::cpu_sub: case rabbitizer::InstrId::UniqueId::cpu_subu: return; default: break; } } else if ((insns[search].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jr) && (insns[search].instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra)) { // stop looking when previous `jr ra` is hit return; } } } // TODO: uniformise use of insn vs insns[i] void pass1(void) { for (size_t i = 0; i < insns.size(); i++) { Insn& insn = insns[i]; // TODO: replace with BAL. Or just fix properly if (insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_bal) { insn.patchAddress(rabbitizer::InstrId::UniqueId::cpu_jal, insn.instruction.getVram() + insn.instruction.getBranchOffset()); } if (insn.instruction.isJump()) { if (insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jal || insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) { uint32_t target = insn.getAddress(); label_addresses.insert(target); add_function(target); } else if (insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jr) { // sltiu $at, $ty, z // sw $reg, offset($sp) (very seldom, one or more, usually in func entry) // lw $gp, offset($sp) (if PIC, and very seldom) // beqz $at, .L // some other instruction (not always) // lui $at, %hi(jtbl) // sll $tx, $ty, 2 // addu $at, $at, $tx // lw $tx, %lo(jtbl)($at) // nop (code compiled with 5.3) // addu $tx, $tx, $gp (if PIC) // jr $tx // IDO 7.1: // lw at,offset(gp) // andi t9,t8,0x3f // sll t9,t9,0x2 // addu at,at,t9 // lw t9,offset(at) // addu t9,t9,gp // jr t9 // IDO 5.3: // lw at,offset(gp) // andi t3,t2,0x3f // sll t3,t3,0x2 // addu at,at,t3 // something // lw t3,offset(at) // something // addu t3,t3,gp // jr t3 if (i >= 7 && rodata_section != NULL) { bool is_pic = (insns[i - 1].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_addu) && (insns[i - 1].instruction.GetO32_rt() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_gp); bool has_nop = insns[i - is_pic - 1].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_nop; bool has_extra = insns[i - is_pic - has_nop - 5].instruction.getUniqueId() != rabbitizer::InstrId::UniqueId::cpu_beqz; int lw = i - (int)is_pic - (int)has_nop - 1; if (insns[lw].instruction.getUniqueId() != rabbitizer::InstrId::UniqueId::cpu_lw) { --lw; } if ((insns[lw].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_lw) && (insns[lw].linked_insn != -1)) { int sltiu_index = -1; int andi_index = -1; uint32_t addu_index = lw - 1; uint32_t num_cases; bool found = false; bool and_variant = false; int end = 14; if (insns[addu_index].instruction.getUniqueId() != rabbitizer::InstrId::UniqueId::cpu_addu) { --addu_index; } if (insns[addu_index].instruction.getUniqueId() != rabbitizer::InstrId::UniqueId::cpu_addu) { goto skip; } if (insns[addu_index - 1].instruction.getUniqueId() != rabbitizer::InstrId::UniqueId::cpu_sll) { goto skip; } if (get_dest_reg(insns[addu_index - 1]) != insn.instruction.GetO32_rs()) { goto skip; } for (int j = 3; j <= 4; j++) { if (insns[lw - j].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_andi) { andi_index = lw - j; break; } } if (i == 368393) { // In copt end = 18; } for (int j = 5; j <= end; j++) { if ((insns[lw - has_extra - j].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_sltiu) && (insns[lw - has_extra - j].instruction.GetO32_rt() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_at)) { sltiu_index = j; break; } if (insns[lw - has_extra - j].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jr) { // Prevent going into a previous switch break; } } if (sltiu_index != -1) { andi_index = -1; } if (sltiu_index != -1 && insns[lw - has_extra - sltiu_index].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_sltiu) { num_cases = insns[lw - has_extra - sltiu_index].instruction.getProcessedImmediate(); found = true; } else if (andi_index != -1) { num_cases = insns[andi_index].instruction.getProcessedImmediate() + 1; found = true; and_variant = true; } else if (i == 219382) { // Special hard case in copt where the initial sltiu is in another basic block found = true; num_cases = 13; } else if (i == 370995) { // Special hard case in copt where the initial sltiu is in another basic block found = true; num_cases = 12; } if (found) { uint32_t jtbl_addr = insns[lw].linked_value; if (is_pic) { insns[i - 1].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); } insn.jtbl_addr = jtbl_addr; insn.num_cases = num_cases; insn.index_reg = insns[addu_index - 1].instruction.GetO32_rt(); insns[lw].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); insns[addu_index].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); insns[addu_index - 1].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); if (!and_variant) { insns[addu_index - 2].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); } if (jtbl_addr < rodata_vaddr || jtbl_addr + num_cases * sizeof(uint32_t) > rodata_vaddr + rodata_section_len) { fprintf(stderr, "jump table outside rodata\n"); exit(EXIT_FAILURE); } for (uint32_t i = 0; i < num_cases; i++) { uint32_t target_addr = read_u32_be(rodata_section + (jtbl_addr - rodata_vaddr) + i * sizeof(uint32_t)); target_addr += gp_value; // printf("%08X\n", target_addr); label_addresses.insert(target_addr); } } skip:; } } } else if (insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jalr) { // empty } else { assert(!"Unreachable code"); } } else if (insn.instruction.isBranch()) { uint32_t target = insn.getAddress(); label_addresses.insert(target); } switch (insns[i].instruction.getUniqueId()) { // find floating point LI case rabbitizer::InstrId::UniqueId::cpu_mtc1: { rabbitizer::Registers::Cpu::GprO32 rt = insns[i].instruction.GetO32_rt(); for (int s = i - 1; s >= 0; s--) { switch (insns[s].instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_lui: if (insns[s].instruction.GetO32_rt() == rt) { float f; uint32_t lui_imm = insns[s].instruction.getProcessedImmediate() << 16; memcpy(&f, &lui_imm, sizeof(f)); // link up the LUI with this instruction and the float insns[s].linked_insn = i; insns[s].linked_float = f; // rewrite LUI instruction to be LI insns[s].lila_dst_reg = get_dest_reg(insns[s]); insns[s].patchInstruction(UniqueId_cpu_li); insns[s].patchImmediate(lui_imm); } goto loop_end; case rabbitizer::InstrId::UniqueId::cpu_lw: case rabbitizer::InstrId::UniqueId::cpu_ld: case rabbitizer::InstrId::UniqueId::cpu_lh: case rabbitizer::InstrId::UniqueId::cpu_lhu: case rabbitizer::InstrId::UniqueId::cpu_lb: case rabbitizer::InstrId::UniqueId::cpu_lbu: case rabbitizer::InstrId::UniqueId::cpu_addiu: if (rt == insns[s].instruction.GetO32_rt()) { goto loop_end; } continue; case rabbitizer::InstrId::UniqueId::cpu_add: case rabbitizer::InstrId::UniqueId::cpu_sub: case rabbitizer::InstrId::UniqueId::cpu_subu: if (rt == insns[s].instruction.GetO32_rd()) { goto loop_end; } continue; case rabbitizer::InstrId::UniqueId::cpu_jr: if (insns[s].instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { goto loop_end; } continue; default: continue; } } loop_end:; } break; case rabbitizer::InstrId::UniqueId::cpu_sd: case rabbitizer::InstrId::UniqueId::cpu_sw: case rabbitizer::InstrId::UniqueId::cpu_sh: case rabbitizer::InstrId::UniqueId::cpu_sb: case rabbitizer::InstrId::UniqueId::cpu_lb: case rabbitizer::InstrId::UniqueId::cpu_lbu: case rabbitizer::InstrId::UniqueId::cpu_ld: case rabbitizer::InstrId::UniqueId::cpu_ldl: case rabbitizer::InstrId::UniqueId::cpu_ldr: case rabbitizer::InstrId::UniqueId::cpu_lh: case rabbitizer::InstrId::UniqueId::cpu_lhu: case rabbitizer::InstrId::UniqueId::cpu_lw: case rabbitizer::InstrId::UniqueId::cpu_lwu: case rabbitizer::InstrId::UniqueId::cpu_ldc1: case rabbitizer::InstrId::UniqueId::cpu_lwc1: case rabbitizer::InstrId::UniqueId::cpu_lwc2: case rabbitizer::InstrId::UniqueId::cpu_swc1: case rabbitizer::InstrId::UniqueId::cpu_swc2: { rabbitizer::Registers::Cpu::GprO32 mem_rs = insns[i].instruction.GetO32_rs(); int32_t mem_imm = insns[i].instruction.getProcessedImmediate(); if (mem_rs == rabbitizer::Registers::Cpu::GprO32::GPR_O32_gp) { unsigned int got_entry = (mem_imm + gp_value_adj) / sizeof(unsigned int); if (got_entry >= got_locals.size()) { got_entry -= got_locals.size(); if (got_entry < got_globals.size()) { assert(insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_lw); unsigned int dest_vaddr = got_globals[got_entry]; insns[i].is_global_got_memop = true; insns[i].linked_value = dest_vaddr; // patch to LA insns[i].lila_dst_reg = get_dest_reg(insns[i]); insns[i].patchAddress(UniqueId_cpu_la, dest_vaddr); } } } else { link_with_lui(i, mem_rs, mem_imm); } } break; case rabbitizer::InstrId::UniqueId::cpu_addiu: case rabbitizer::InstrId::UniqueId::cpu_ori: { // could be insn? rabbitizer::Registers::Cpu::GprO32 rt = insns[i].instruction.GetO32_rt(); rabbitizer::Registers::Cpu::GprO32 rs = insns[i].instruction.GetO32_rs(); int32_t imm = insns[i].instruction.getProcessedImmediate(); if (rs == rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero) { // becomes LI insns[i].lila_dst_reg = get_dest_reg(insns[i]); insns[i].patchInstruction(UniqueId_cpu_li); insns[i].patchImmediate(imm); } else if (rt != rabbitizer::Registers::Cpu::GprO32::GPR_O32_gp) { // only look for LUI if rt and rs are // the same link_with_lui(i, rs, imm); } } break; case rabbitizer::InstrId::UniqueId::cpu_jalr: { rabbitizer::Registers::Cpu::GprO32 rs = insn.instruction.GetO32_rs(); if (rs == rabbitizer::Registers::Cpu::GprO32::GPR_O32_t9) { link_with_jalr(i); if (insn.linked_insn != -1) { insn.patchAddress(rabbitizer::InstrId::UniqueId::cpu_jal, insn.linked_value); label_addresses.insert(insn.linked_value); add_function(insn.linked_value); } } } break; default: break; } if ((insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_addu) && (insn.instruction.GetO32_rd() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_gp) && (insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_gp) && (insn.instruction.GetO32_rt() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_t9) && i >= 2) { for (size_t j = i - 2; j <= i; j++) { insns[j].patchInstruction(rabbitizer::InstrId::UniqueId::cpu_nop); } } } } uint32_t addr_to_i(uint32_t addr) { return (addr - text_vaddr) / 4; } void pass2(void) { // Find returns in each function for (size_t i = 0; i < insns.size(); i++) { uint32_t addr = text_vaddr + i * 4; Insn& insn = insns[i]; if ((insn.instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jr) && (insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra)) { auto it = find_function(addr); assert(it != functions.end()); it->second.returns.push_back(addr + 4); } if (insn.instruction.getUniqueId() == UniqueId_cpu_la) { uint32_t faddr = insn.getAddress(); if ((text_vaddr <= faddr) && (faddr < text_vaddr + text_section_len)) { la_function_pointers.insert(faddr); functions[faddr].referenced_by_function_pointer = true; #if INSPECT_FUNCTION_POINTERS fprintf(stderr, "la function pointer: 0x%x at 0x%x\n", faddr, addr); #endif } } } for (auto it = functions.begin(); it != functions.end(); ++it) { if (it->second.returns.size() == 0) { uint32_t i = addr_to_i(it->first); auto str_it = symbol_names.find(it->first); if (str_it != symbol_names.end() && str_it->second == "__start") { } else if (str_it != symbol_names.end() && str_it->second == "xmalloc") { // orig 5.3: /* 496bf4: 3c1c0fb9 lui gp,0xfb9 496bf8: 279c366c addiu gp,gp,13932 496bfc: 0399e021 addu gp,gp,t9 496c00: 27bdffd8 addiu sp,sp,-40 496c04: 8f858de8 lw a1,-29208(gp) 496c08: 10000006 b 496c24 496c0c: afbf0020 sw ra,32(sp) */ // jal alloc_new // lui $a1, malloc_scb // jr $ra // nop uint32_t alloc_new_addr = text_vaddr + (i + 7) * 4; insns[i].patchAddress(rabbitizer::InstrId::UniqueId::cpu_jal, alloc_new_addr); assert(symbol_names.count(alloc_new_addr) && symbol_names[alloc_new_addr] == "alloc_new"); i++; // LA if (insns[i + 5].instruction.getUniqueId() == UniqueId_cpu_la) { // 7.1 insns[i] = insns[i + 5]; } else { // 5.3 insns[i] = insns[i + 3]; } i++; // JR $RA insns[i].patched = true; insns[i].instruction = rabbitizer::InstructionCpu(0x03E00008, insns[i].instruction.getVram()); it->second.returns.push_back(text_vaddr + i * 4 + 4); i++; for (uint32_t j = 0; j < 4; j++) { // NOP insns[i].patched = true; insns[i].instruction = rabbitizer::InstructionCpu(0, insns[i].instruction.getVram()); i++; } } else if (str_it != symbol_names.end() && str_it->second == "xfree") { // jal alloc_dispose // lui $a1, malloc_scb // jr $ra // nop uint32_t alloc_dispose_addr = text_vaddr + (i + 4) * 4; if (symbol_names.count(alloc_dispose_addr + 4) && symbol_names[alloc_dispose_addr + 4] == "alloc_dispose") { alloc_dispose_addr += 4; } insns[i].patchAddress(rabbitizer::InstrId::UniqueId::cpu_jal, alloc_dispose_addr); assert(symbol_names.count(alloc_dispose_addr) && symbol_names[alloc_dispose_addr] == "alloc_dispose"); i++; insns[i] = insns[i + 2]; i++; // JR $RA insns[i].patched = true; insns[i].instruction = rabbitizer::InstructionCpu(0x03E00008, insns[i].instruction.getVram()); it->second.returns.push_back(text_vaddr + i * 4 + 4); i++; // NOP insns[i].patched = true; insns[i].instruction = rabbitizer::InstructionCpu(0, insns[i].instruction.getVram()); } else if ((insns[i].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_lw) && (insns[i + 1].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_move) && (insns[i + 2].instruction.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_jalr)) { /* 408f50: 8f998010 lw t9,-32752(gp) 408f54: 03e07821 move t7,ra 408f58: 0320f809 jalr t9 */ } else if (it->first > mcount_addr) { fprintf(stderr, "no ret: 0x%x\n", it->first); abort(); } } auto next = it; ++next; if (next == functions.end()) { it->second.end_addr = text_vaddr + text_section_len; } else { it->second.end_addr = next->first; } } } void add_edge(uint32_t from, uint32_t to, bool function_entry = false, bool function_exit = false, bool extern_function = false, bool function_pointer = false) { Edge fe = Edge(), be = Edge(); fe.i = to; be.i = from; fe.function_entry = function_entry; be.function_entry = function_entry; fe.function_exit = function_exit; be.function_exit = function_exit; fe.extern_function = extern_function; be.extern_function = extern_function; fe.function_pointer = function_pointer; be.function_pointer = function_pointer; insns[from].successors.push_back(fe); insns[to].predecessors.push_back(be); } void pass3(void) { // Build graph for (size_t i = 0; i < insns.size(); i++) { uint32_t addr = text_vaddr + i * 4; Insn& insn = insns[i]; if (insn.no_following_successor) { continue; } switch (insn.instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_beq: case rabbitizer::InstrId::UniqueId::cpu_bgez: case rabbitizer::InstrId::UniqueId::cpu_bgtz: case rabbitizer::InstrId::UniqueId::cpu_blez: case rabbitizer::InstrId::UniqueId::cpu_bltz: case rabbitizer::InstrId::UniqueId::cpu_bne: case rabbitizer::InstrId::UniqueId::cpu_beqz: case rabbitizer::InstrId::UniqueId::cpu_bnez: case rabbitizer::InstrId::UniqueId::cpu_bc1f: case rabbitizer::InstrId::UniqueId::cpu_bc1t: add_edge(i, i + 1); add_edge(i + 1, addr_to_i(insn.getAddress())); break; case rabbitizer::InstrId::UniqueId::cpu_beql: case rabbitizer::InstrId::UniqueId::cpu_bgezl: case rabbitizer::InstrId::UniqueId::cpu_bgtzl: case rabbitizer::InstrId::UniqueId::cpu_blezl: case rabbitizer::InstrId::UniqueId::cpu_bltzl: case rabbitizer::InstrId::UniqueId::cpu_bnel: case rabbitizer::InstrId::UniqueId::cpu_bc1fl: case rabbitizer::InstrId::UniqueId::cpu_bc1tl: add_edge(i, i + 1); add_edge(i, i + 2); add_edge(i + 1, addr_to_i(insn.getAddress())); insns[i + 1].no_following_successor = true; // don't inspect delay slot break; case rabbitizer::InstrId::UniqueId::cpu_b: case rabbitizer::InstrId::UniqueId::cpu_j: add_edge(i, i + 1); add_edge(i + 1, addr_to_i(insn.getAddress())); insns[i + 1].no_following_successor = true; // don't inspect delay slot break; case rabbitizer::InstrId::UniqueId::cpu_jr: { add_edge(i, i + 1); if (insn.jtbl_addr != 0) { uint32_t jtbl_pos = insn.jtbl_addr - rodata_vaddr; assert(jtbl_pos < rodata_section_len && jtbl_pos + insn.num_cases * sizeof(uint32_t) <= rodata_section_len); for (uint32_t j = 0; j < insn.num_cases; j++) { uint32_t dest_addr = read_u32_be(rodata_section + jtbl_pos + j * sizeof(uint32_t)) + gp_value; add_edge(i + 1, addr_to_i(dest_addr)); } } else { assert(insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra && "jump to address in register not supported"); } insns[i + 1].no_following_successor = true; // don't inspect delay slot break; } case rabbitizer::InstrId::UniqueId::cpu_jal: { add_edge(i, i + 1); uint32_t dest = insn.getAddress(); if (dest > mcount_addr && dest >= text_vaddr && dest < text_vaddr + text_section_len) { add_edge(i + 1, addr_to_i(dest), true); auto it = functions.find(dest); assert(it != functions.end()); for (uint32_t ret_instr : it->second.returns) { add_edge(addr_to_i(ret_instr), i + 2, false, true); } } else { add_edge(i + 1, i + 2, false, false, true); } insns[i + 1].no_following_successor = true; // don't inspect delay slot break; } case rabbitizer::InstrId::UniqueId::cpu_jalr: // function pointer add_edge(i, i + 1); add_edge(i + 1, i + 2, false, false, false, true); insns[i + 1].no_following_successor = true; // don't inspect delay slot break; default: add_edge(i, i + 1); break; } } } #define GPR_O32_hi (rabbitizer::Registers::Cpu::GprO32)((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra + 1) #define GPR_O32_lo (rabbitizer::Registers::Cpu::GprO32)((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra + 2) uint64_t map_reg(rabbitizer::Registers::Cpu::GprO32 reg) { return (uint64_t)1 << ((int)reg - (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero + 1); } uint64_t temporary_regs(void) { // clang-format off return map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t4) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t5) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t6) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t7) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t8) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_t9); // clang-format on } typedef enum { /* 0 */ TYPE_NOP, // No arguments /* 1 */ TYPE_S, // in /* 2 */ TYPE_D, // 1 out /* 3 */ TYPE_D_S, // out, in } TYPE; TYPE insn_to_type(Insn& insn) { switch (insn.instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_add_s: case rabbitizer::InstrId::UniqueId::cpu_add_d: return TYPE_NOP; case rabbitizer::InstrId::UniqueId::cpu_add: case rabbitizer::InstrId::UniqueId::cpu_addu: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_addi: case rabbitizer::InstrId::UniqueId::cpu_addiu: case rabbitizer::InstrId::UniqueId::cpu_andi: case rabbitizer::InstrId::UniqueId::cpu_ori: case rabbitizer::InstrId::UniqueId::cpu_lb: case rabbitizer::InstrId::UniqueId::cpu_lbu: case rabbitizer::InstrId::UniqueId::cpu_lh: case rabbitizer::InstrId::UniqueId::cpu_lhu: case rabbitizer::InstrId::UniqueId::cpu_lw: case rabbitizer::InstrId::UniqueId::cpu_lwl: // case rabbitizer::InstrId::UniqueId::cpu_lwr: case rabbitizer::InstrId::UniqueId::cpu_move: case rabbitizer::InstrId::UniqueId::cpu_negu: case rabbitizer::InstrId::UniqueId::cpu_not: case rabbitizer::InstrId::UniqueId::cpu_sll: case rabbitizer::InstrId::UniqueId::cpu_slti: case rabbitizer::InstrId::UniqueId::cpu_sltiu: case rabbitizer::InstrId::UniqueId::cpu_sra: case rabbitizer::InstrId::UniqueId::cpu_srl: case rabbitizer::InstrId::UniqueId::cpu_xori: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_mfhi: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_mflo: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_and: case rabbitizer::InstrId::UniqueId::cpu_or: case rabbitizer::InstrId::UniqueId::cpu_nor: case rabbitizer::InstrId::UniqueId::cpu_sllv: case rabbitizer::InstrId::UniqueId::cpu_slt: case rabbitizer::InstrId::UniqueId::cpu_sltu: case rabbitizer::InstrId::UniqueId::cpu_srav: case rabbitizer::InstrId::UniqueId::cpu_srlv: case rabbitizer::InstrId::UniqueId::cpu_subu: case rabbitizer::InstrId::UniqueId::cpu_xor: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_cfc1: case rabbitizer::InstrId::UniqueId::cpu_mfc1: case UniqueId_cpu_li: case UniqueId_cpu_la: case rabbitizer::InstrId::UniqueId::cpu_lui: return TYPE_D; case rabbitizer::InstrId::UniqueId::cpu_ctc1: case rabbitizer::InstrId::UniqueId::cpu_bgez: case rabbitizer::InstrId::UniqueId::cpu_bgezl: case rabbitizer::InstrId::UniqueId::cpu_bgtz: case rabbitizer::InstrId::UniqueId::cpu_bgtzl: case rabbitizer::InstrId::UniqueId::cpu_blez: case rabbitizer::InstrId::UniqueId::cpu_blezl: case rabbitizer::InstrId::UniqueId::cpu_bltz: case rabbitizer::InstrId::UniqueId::cpu_bltzl: case rabbitizer::InstrId::UniqueId::cpu_beqz: case rabbitizer::InstrId::UniqueId::cpu_bnez: case rabbitizer::InstrId::UniqueId::cpu_mtc1: return TYPE_S; case rabbitizer::InstrId::UniqueId::cpu_beq: case rabbitizer::InstrId::UniqueId::cpu_beql: case rabbitizer::InstrId::UniqueId::cpu_bne: case rabbitizer::InstrId::UniqueId::cpu_bnel: case rabbitizer::InstrId::UniqueId::cpu_sb: case rabbitizer::InstrId::UniqueId::cpu_sh: case rabbitizer::InstrId::UniqueId::cpu_sw: case rabbitizer::InstrId::UniqueId::cpu_swl: // case rabbitizer::InstrId::UniqueId::cpu_swr: case rabbitizer::InstrId::UniqueId::cpu_tne: case rabbitizer::InstrId::UniqueId::cpu_teq: case rabbitizer::InstrId::UniqueId::cpu_tge: case rabbitizer::InstrId::UniqueId::cpu_tgeu: case rabbitizer::InstrId::UniqueId::cpu_tlt: return TYPE_S; case rabbitizer::InstrId::UniqueId::cpu_div: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_div_s: case rabbitizer::InstrId::UniqueId::cpu_div_d: return TYPE_NOP; case rabbitizer::InstrId::UniqueId::cpu_divu: case rabbitizer::InstrId::UniqueId::cpu_mult: case rabbitizer::InstrId::UniqueId::cpu_multu: return TYPE_D_S; case rabbitizer::InstrId::UniqueId::cpu_neg_s: case rabbitizer::InstrId::UniqueId::cpu_neg_d: return TYPE_NOP; case rabbitizer::InstrId::UniqueId::cpu_jalr: return TYPE_S; case rabbitizer::InstrId::UniqueId::cpu_jr: if (insn.jtbl_addr != 0) { insn.instruction.Set_rs(insn.index_reg); } if (insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { return TYPE_NOP; } return TYPE_S; case rabbitizer::InstrId::UniqueId::cpu_lwc1: case rabbitizer::InstrId::UniqueId::cpu_ldc1: case rabbitizer::InstrId::UniqueId::cpu_swc1: case rabbitizer::InstrId::UniqueId::cpu_sdc1: return TYPE_S; default: return TYPE_NOP; } } uint64_t get_dest_reg_mask(const Insn& insn) { switch (insn.instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_div: case rabbitizer::InstrId::UniqueId::cpu_divu: case rabbitizer::InstrId::UniqueId::cpu_mult: case rabbitizer::InstrId::UniqueId::cpu_multu: return map_reg(GPR_O32_lo) | map_reg(GPR_O32_hi); default: return map_reg(get_dest_reg(insn)); } } uint64_t get_single_source_reg_mask(const rabbitizer::InstructionCpu& instr) { switch (instr.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_mflo: return map_reg(GPR_O32_lo); case rabbitizer::InstrId::UniqueId::cpu_mfhi: return map_reg(GPR_O32_hi); default: break; } if (instr.hasOperandAlias(rabbitizer::OperandType::cpu_rs)) { return map_reg(instr.GetO32_rs()); } else if (instr.hasOperandAlias(rabbitizer::OperandType::cpu_rt)) { return map_reg(instr.GetO32_rt()); } else { return 0; } } uint64_t get_all_source_reg_mask(const rabbitizer::InstructionCpu& instr) { uint64_t ret = 0; switch (instr.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_mflo: ret |= map_reg(GPR_O32_lo); case rabbitizer::InstrId::UniqueId::cpu_mfhi: ret |= map_reg(GPR_O32_hi); default: break; } if (instr.hasOperandAlias(rabbitizer::OperandType::cpu_rs)) { ret |= map_reg(instr.GetO32_rs()); } if (instr.hasOperandAlias(rabbitizer::OperandType::cpu_rt) && !instr.modifiesRt()) { ret |= map_reg(instr.GetO32_rt()); } return ret; } void pass4(void) { vector q; // TODO: Why is this called q? uint64_t livein_func_start = 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_sp) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero); q.push_back(main_addr); insns[addr_to_i(main_addr)].f_livein = livein_func_start; for (auto& it : data_function_pointers) { q.push_back(it.second); insns[addr_to_i(it.second)].f_livein = livein_func_start | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3); } for (auto& addr : la_function_pointers) { q.push_back(addr); insns[addr_to_i(addr)].f_livein = livein_func_start | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3); } while (!q.empty()) { uint32_t addr = q.back(); q.pop_back(); uint32_t i = addr_to_i(addr); Insn& insn = insns[i]; uint64_t live = insn.f_livein | 1U; uint64_t src_regs_map; switch (insn_to_type(insn)) { case TYPE_D: live |= get_dest_reg_mask(insn); break; case TYPE_D_S: src_regs_map = get_all_source_reg_mask(insn.instruction); if ((live & src_regs_map) == src_regs_map) { live |= get_dest_reg_mask(insn); } break; case TYPE_S: case TYPE_NOP: break; } if ((insn.f_liveout | live) == insn.f_liveout) { // No new bits continue; } live |= insn.f_liveout; insn.f_liveout = live; bool function_entry = false; for (Edge& e : insn.successors) { uint64_t new_live = live; if (e.function_exit) { new_live &= 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero); } else if (e.function_entry) { new_live &= 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_sp) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero); function_entry = true; } else if (e.extern_function) { string_view name; size_t extern_function_id; uint32_t address = insns[i - 1].getAddress(); // TODO: Can this only ever be a J-type instruction? auto it = symbol_names.find(address); const ExternFunction* found_fn = nullptr; if (it != symbol_names.end()) { name = it->second; for (auto& fn : extern_functions) { if (name == fn.name) { found_fn = &fn; break; } } if (found_fn == nullptr) { fprintf(stderr, "missing extern function: %s\n", string(name).c_str()); } } assert(found_fn); char ret_type = found_fn->params[0]; new_live &= ~(map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | temporary_regs()); switch (ret_type) { case 'i': case 'u': case 'p': new_live |= map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0); break; case 'f': break; case 'd': break; case 'v': break; case 'l': case 'j': new_live |= map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1); break; } } else if (e.function_pointer) { new_live &= ~(map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | temporary_regs()); new_live |= map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1); } if ((insns[e.i].f_livein | new_live) != insns[e.i].f_livein) { insns[e.i].f_livein |= new_live; q.push_back(text_vaddr + e.i * sizeof(uint32_t)); } } if (function_entry) { // add one edge that skips the function call, for callee-saved register liveness propagation live &= ~(map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | temporary_regs()); if ((insns[i + 1].f_livein | live) != insns[i + 1].f_livein) { insns[i + 1].f_livein |= live; q.push_back(text_vaddr + (i + 1) * sizeof(uint32_t)); } } } } void pass5(void) { vector q; assert(functions.count(main_addr)); q = functions[main_addr].returns; for (auto addr : q) { insns[addr_to_i(addr)].b_liveout = 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0); } for (auto& it : data_function_pointers) { for (auto addr : functions[it.second].returns) { q.push_back(addr); insns[addr_to_i(addr)].b_liveout = 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1); } } for (auto& func_addr : la_function_pointers) { for (auto addr : functions[func_addr].returns) { q.push_back(addr); insns[addr_to_i(addr)].b_liveout = 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1); } } for (auto& insn : insns) { if (insn.f_livein != 0) { q.push_back(insn.instruction.getVram()); } } while (!q.empty()) { uint32_t addr = q.back(); q.pop_back(); uint32_t i = addr_to_i(addr); Insn& insn = insns[i]; uint64_t live = insn.b_liveout | 1; switch (insn_to_type(insn)) { case TYPE_S: live |= get_all_source_reg_mask(insn.instruction); break; case TYPE_D: live &= ~get_dest_reg_mask(insn); break; case TYPE_D_S: if (live & get_dest_reg_mask(insn)) { live &= ~get_dest_reg_mask(insn); live |= get_all_source_reg_mask(insn.instruction); } break; case TYPE_NOP: break; } if ((insn.b_livein | live) == insn.b_livein) { // No new bits continue; } live |= insn.b_livein; insn.b_livein = live; bool function_exit = false; for (Edge& e : insn.predecessors) { uint64_t new_live = live; if (e.function_exit) { new_live &= 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1); function_exit = true; } else if (e.function_entry) { new_live &= 1U | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_sp); } else if (e.extern_function) { string_view name; size_t extern_function_id; const ExternFunction* found_fn = nullptr; uint32_t address = insns[i - 2].getAddress(); // TODO: Can this only ever be a J-type instruction? auto it = symbol_names.find(address); if (it != symbol_names.end()) { name = it->second; for (auto& fn : extern_functions) { if (name == fn.name) { found_fn = &fn; break; } } } assert(found_fn); uint64_t args = 1U; if (found_fn->flags & FLAG_VARARG) { // Assume the worst, that all four registers are used for (int j = 0; j < 4; j++) { args |= map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + j)); } } int pos = 0; int pos_float = 0; bool only_floats_so_far = true; for (const char* p = found_fn->params + 1; *p != '\0'; ++p) { switch (*p) { case 'i': case 'u': case 'p': case 't': only_floats_so_far = false; if (pos < 4) { args |= map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos)); } ++pos; break; case 'f': if (only_floats_so_far && pos_float < 4) { pos_float += 2; } else if (pos < 4) { args |= map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos)); } ++pos; break; case 'd': // !!! if (pos % 1 != 0) { ++pos; } if (only_floats_so_far && pos_float < 4) { pos_float += 2; } else if (pos < 4) { args |= map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos)) | map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos + 1)); } pos += 2; break; case 'l': case 'j': if (pos % 1 != 0) { ++pos; } only_floats_so_far = false; if (pos < 4) { args |= map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos)) | map_reg((rabbitizer::Registers::Cpu::GprO32)( (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos + 1)); } pos += 2; break; } } args |= map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_sp); new_live &= ~(map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | temporary_regs()); new_live |= args; } else if (e.function_pointer) { new_live &= ~(map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | temporary_regs()); new_live |= map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3); } if ((insns[e.i].b_liveout | new_live) != insns[e.i].b_liveout) { insns[e.i].b_liveout |= new_live; q.push_back(text_vaddr + e.i * sizeof(uint32_t)); } } if (function_exit) { // add one edge that skips the function call, for callee-saved register liveness propagation live &= ~(map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3) | map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1) | temporary_regs()); if ((insns[i - 1].b_liveout | live) != insns[i - 1].b_liveout) { insns[i - 1].b_liveout |= live; q.push_back(text_vaddr + (i - 1) * sizeof(uint32_t)); } } } } void pass6(void) { for (auto& it : functions) { uint32_t addr = it.first; Function& f = it.second; for (uint32_t ret : f.returns) { Insn& i = insns[addr_to_i(ret)]; if (i.f_liveout & i.b_liveout & map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1)) { f.nret = 2; } else if ((i.f_liveout & i.b_liveout & map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)) && f.nret == 0) { f.nret = 1; } } Insn& insn = insns.at(addr_to_i(addr)); for (int i = 0; i < 4; i++) { if (insn.f_livein & insn.b_livein & map_reg( (rabbitizer::Registers::Cpu::GprO32)((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + i))) { f.nargs = 1 + i; } } f.v0_in = (insn.f_livein & insn.b_livein & map_reg(rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)) != 0 && !f.referenced_by_function_pointer; } } void dump(void) { for (size_t i = 0; i < insns.size(); i++) { Insn& insn = insns[i]; uint32_t vaddr = text_vaddr + i * sizeof(uint32_t); if (label_addresses.count(vaddr)) { if (symbol_names.count(vaddr)) { printf("L%08x: //%s\n", vaddr, symbol_names[vaddr].c_str()); } else { printf("L%08x:\n", vaddr); } } // TODO: construct an immediate override for the instructions printf("\t%s", insn.disassemble().c_str()); if (insn.patched) { printf("\t[patched, immediate now 0x%X]", insn.patched_addr); } printf("\n"); } } const char* r(uint32_t reg) { static const char* regs[] = { /* */ "zero", "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", }; assert(reg < std::size(regs)); return regs[reg]; } const char* wr(uint32_t reg) { // clang-format off static const char *regs[] = { "f0.w[0]", "f0.w[1]", "f2.w[0]", "f2.w[1]", "f4.w[0]", "f4.w[1]", "f6.w[0]", "f6.w[1]", "f8.w[0]", "f8.w[1]", "f10.w[0]", "f10.w[1]", "f12.w[0]", "f12.w[1]", "f14.w[0]", "f14.w[1]", "f16.w[0]", "f16.w[1]", "f18.w[0]", "f18.w[1]", "f20.w[0]", "f20.w[1]", "f22.w[0]", "f22.w[1]", "f24.w[0]", "f24.w[1]", "f26.w[0]", "f26.w[1]", "f28.w[0]", "f28.w[1]", "f30.w[0]", "f30.w[1]" }; // clang-format on size_t index = reg - (int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0; assert(index < std::size(regs)); return regs[index]; } const char* fr(uint32_t reg) { // clang-format off static const char *regs[] = { "f0.f[0]", "f0.f[1]", "f2.f[0]", "f2.f[1]", "f4.f[0]", "f4.f[1]", "f6.f[0]", "f6.f[1]", "f8.f[0]", "f8.f[1]", "f10.f[0]", "f10.f[1]", "f12.f[0]", "f12.f[1]", "f14.f[0]", "f14.f[1]", "f16.f[0]", "f16.f[1]", "f18.f[0]", "f18.f[1]", "f20.f[0]", "f20.f[1]", "f22.f[0]", "f22.f[1]", "f24.f[0]", "f24.f[1]", "f26.f[0]", "f26.f[1]", "f28.f[0]", "f28.f[1]", "f30.f[0]", "f30.f[1]", }; // clang-format on size_t index = reg - (int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0; assert(index < std::size(regs)); return regs[index]; } const char* dr(uint32_t reg) { // clang-format off static const char *regs[] = { "f0", "f2", "f4", "f6", "f8", "f10", "f12", "f14", "f16", "f18", "f20", "f22", "f24", "f26", "f28", "f30" }; // clang-format on size_t index = reg - (int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0; assert(index % 2 == 0); index /= 2; assert(index < std::size(regs)); return regs[index]; } void dump_instr(int i); void dump_cond_branch(int i, const char* lhs, const char* op, const char* rhs) { Insn& insn = insns[i]; const char* cast1 = ""; const char* cast2 = ""; if (strcmp(op, "==") && strcmp(op, "!=")) { cast1 = "(int)"; if (strcmp(rhs, "0")) { cast2 = "(int)"; } } printf("if (%s%s %s %s%s) {\n", cast1, lhs, op, cast2, rhs); dump_instr(i + 1); uint32_t addr = insn.getAddress(); printf("goto L%x;}\n", addr); } void dump_cond_branch_likely(int i, const char* lhs, const char* op, const char* rhs) { uint32_t target = text_vaddr + (i + 2) * sizeof(uint32_t); dump_cond_branch(i, lhs, op, rhs); if (!TRACE) { printf("else goto L%x;\n", target); } else { printf("else {printf(\"pc=0x%08x (ignored)\\n\"); goto L%x;}\n", text_vaddr + (i + 1) * 4, target); } label_addresses.insert(target); } void dump_jal(int i, uint32_t imm) { string_view name; auto it = symbol_names.find(imm); const ExternFunction* found_fn = nullptr; // Check for an external function at the address in the immediate. If it does not exist, function is internal if (it != symbol_names.end()) { name = it->second; for (auto& fn : extern_functions) { if (name == fn.name) { found_fn = &fn; break; } } } dump_instr(i + 1); if (found_fn != nullptr) { if (found_fn->flags & FLAG_VARARG) { for (int j = 0; j < 4; j++) { printf("MEM_U32(sp + %d) = %s;\n", j * 4, r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + j)); } } const char ret_type = found_fn->params[0]; switch (ret_type) { case 'v': break; case 'i': case 'u': case 'p': printf("%s = ", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)); break; case 'f': printf("%s = ", fr((int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0)); break; case 'd': printf("tempf64 = "); break; case 'l': case 'j': printf("temp64 = "); break; } printf("wrapper_%s(", string(name).c_str()); bool first = true; if (!(found_fn->flags & FLAG_NO_MEM)) { printf("mem"); first = false; } int pos = 0; int pos_float = 0; bool only_floats_so_far = true; bool needs_sp = false; for (const char* p = &found_fn->params[1]; *p != '\0'; ++p) { if (!first) { printf(", "); } first = false; switch (*p) { case 't': printf("trampoline, "); needs_sp = true; // fallthrough case 'i': case 'u': case 'p': only_floats_so_far = false; if (pos < 4) { printf("%s", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos)); } else { printf("MEM_%c32(sp + %d)", *p == 'i' ? 'S' : 'U', pos * 4); } ++pos; break; case 'f': if (only_floats_so_far && pos_float < 4) { printf("%s", fr((int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fa0 + pos_float)); pos_float += 2; } else if (pos < 4) { printf("BITCAST_U32_TO_F32(%s)", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos)); } else { printf("BITCAST_U32_TO_F32(MEM_U32(sp + %d))", pos * 4); } ++pos; break; case 'd': if (pos % 1 != 0) { ++pos; } if (only_floats_so_far && pos_float < 4) { printf("double_from_FloatReg(%s)", dr((int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fa0 + pos_float)); pos_float += 2; } else if (pos < 4) { printf("BITCAST_U64_TO_F64(((uint64_t)%s << 32) | (uint64_t)%s)", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos), r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos + 1)); } else { printf("BITCAST_U64_TO_F64(((uint64_t)MEM_U32(sp + %d) << 32) | " "(uint64_t)MEM_U32(sp + " "%d))", pos * 4, (pos + 1) * 4); } pos += 2; break; case 'l': case 'j': if (pos % 1 != 0) { ++pos; } only_floats_so_far = false; if (*p == 'l') { printf("(int64_t)"); } if (pos < 4) { printf("(((uint64_t)%s << 32) | (uint64_t)%s)", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos), r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + pos + 1)); } else { printf("(((uint64_t)MEM_U32(sp + %d) << 32) | (uint64_t)MEM_U32(sp + %d))", pos * 4, (pos + 1) * 4); } pos += 2; break; } } if ((found_fn->flags & FLAG_VARARG) || needs_sp) { printf("%s%s", first ? "" : ", ", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_sp)); } printf(");\n"); if (ret_type == 'l' || ret_type == 'j') { printf("%s = (uint32_t)(temp64 >> 32);\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)); printf("%s = (uint32_t)temp64;\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1)); } else if (ret_type == 'd') { printf("%s = FloatReg_from_double(tempf64);\n", dr((int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0)); } } else { Function& f = functions.find(imm)->second; if (f.nret == 1) { printf("v0 = "); } else if (f.nret == 2) { printf("temp64 = "); } if (!name.empty()) { printf("f_%s", string(name).c_str()); } else { printf("func_%x", imm); } printf("(mem, sp"); if (f.v0_in) { printf(", %s", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)); } for (uint32_t i = 0; i < f.nargs; i++) { printf(", %s", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + i)); } printf(");\n"); if (f.nret == 2) { printf("%s = (uint32_t)(temp64 >> 32);\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)); printf("%s = (uint32_t)temp64;\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1)); } } printf("goto L%x;\n", text_vaddr + (i + 2) * 4); label_addresses.insert(text_vaddr + (i + 2) * 4); } void dump_instr(int i) { Insn& insn = insns[i]; const char* symbol_name = NULL; if (symbol_names.count(text_vaddr + i * sizeof(uint32_t)) != 0) { symbol_name = symbol_names[text_vaddr + i * sizeof(uint32_t)].c_str(); printf("//%s:\n", symbol_name); } if (TRACE) { printf("++cnt; printf(\"pc=0x%08x%s%s\\n\"); ", text_vaddr + i * 4, symbol_name ? " " : "", symbol_name ? symbol_name : ""); } uint64_t src_regs_map; if (!insn.instruction.isJump() && !insn.instruction.isBranch() && !conservative) { switch (insn_to_type(insn)) { case TYPE_S: src_regs_map = get_all_source_reg_mask(insn.instruction); if (!((insn.f_livein & src_regs_map) == src_regs_map)) { printf("// fdead %llx ", (unsigned long long)insn.f_livein); } break; case TYPE_D_S: { uint64_t reg_mask = get_all_source_reg_mask(insn.instruction); if ((insn.f_livein & reg_mask) != reg_mask) { printf("// fdead %llx ", (unsigned long long)insn.f_livein); break; } } // fallthrough case TYPE_D: if (!(insn.b_liveout & get_dest_reg_mask(insn))) { #if 0 printf("// %i bdead %llx %llx ", i, (unsigned long long)insn.b_liveout, (unsigned long long)get_dest_reg_mask(insn)); #else printf("// bdead %llx ", (unsigned long long)insn.b_liveout); #endif } break; case TYPE_NOP: break; } } int32_t imm; char buf[0x100]; switch (insn.instruction.getUniqueId()) { case rabbitizer::InstrId::UniqueId::cpu_add: case rabbitizer::InstrId::UniqueId::cpu_addu: if (insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero) { printf("%s = %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt())); } else if (insn.instruction.GetO32_rt() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero) { printf("%s = %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs())); } else { printf("%s = %s + %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); } break; case rabbitizer::InstrId::UniqueId::cpu_add_s: printf("%s = %s + %s;\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_add_d: printf("%s = FloatReg_from_double(double_from_FloatReg(%s) + double_from_FloatReg(%s));\n", dr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_addi: case rabbitizer::InstrId::UniqueId::cpu_addiu: imm = insn.getImmediate(); if (insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero) { printf("%s = 0x%x;\n", r((int)insn.instruction.GetO32_rt()), imm); } else { printf("%s = %s + 0x%x;\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); } break; case rabbitizer::InstrId::UniqueId::cpu_and: printf("%s = %s & %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_andi: imm = insn.getImmediate(); printf("%s = %s & 0x%x;\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_beq: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), "==", r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_beql: dump_cond_branch_likely(i, r((int)insn.instruction.GetO32_rs()), "==", r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_bgez: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), ">=", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_bgezl: dump_cond_branch_likely(i, r((int)insn.instruction.GetO32_rs()), ">=", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_bgtz: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), ">", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_bgtzl: dump_cond_branch_likely(i, r((int)insn.instruction.GetO32_rs()), ">", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_blez: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), "<=", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_blezl: dump_cond_branch_likely(i, r((int)insn.instruction.GetO32_rs()), "<=", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_bltz: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), "<", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_bltzl: dump_cond_branch_likely(i, r((int)insn.instruction.GetO32_rs()), "<", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_bne: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), "!=", r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_bnel: dump_cond_branch_likely(i, r((int)insn.instruction.GetO32_rs()), "!=", r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_break: printf("abort();\n"); break; case rabbitizer::InstrId::UniqueId::cpu_beqz: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), "==", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_b: dump_instr(i + 1); imm = insn.getAddress(); printf("goto L%x;\n", imm); break; case rabbitizer::InstrId::UniqueId::cpu_bc1f: printf("if (!cf) {\n"); dump_instr(i + 1); imm = insn.getAddress(); printf("goto L%x;}\n", imm); break; case rabbitizer::InstrId::UniqueId::cpu_bc1t: printf("if (cf) {\n"); dump_instr(i + 1); imm = insn.getAddress(); printf("goto L%x;}\n", imm); break; case rabbitizer::InstrId::UniqueId::cpu_bc1fl: { uint32_t target = text_vaddr + (i + 2) * sizeof(uint32_t); printf("if (!cf) {\n"); dump_instr(i + 1); imm = insn.getAddress(); printf("goto L%x;}\n", imm); if (!TRACE) { printf("else goto L%x;\n", target); } else { printf("else {printf(\"pc=0x%08x (ignored)\\n\"); goto L%x;}\n", text_vaddr + (i + 1) * 4, target); } label_addresses.insert(target); } break; case rabbitizer::InstrId::UniqueId::cpu_bc1tl: { uint32_t target = text_vaddr + (i + 2) * sizeof(uint32_t); printf("if (cf) {\n"); dump_instr(i + 1); imm = insn.getAddress(); printf("goto L%x;}\n", imm); if (!TRACE) { printf("else goto L%x;\n", target); } else { printf("else {printf(\"pc=0x%08x (ignored)\\n\"); goto L%x;}\n", text_vaddr + (i + 1) * 4, target); } label_addresses.insert(target); } break; case rabbitizer::InstrId::UniqueId::cpu_bnez: dump_cond_branch(i, r((int)insn.instruction.GetO32_rs()), "!=", "0"); break; case rabbitizer::InstrId::UniqueId::cpu_c_lt_s: printf("cf = %s < %s;\n", fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_c_le_s: printf("cf = %s <= %s;\n", fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_c_eq_s: printf("cf = %s == %s;\n", fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_c_lt_d: printf("cf = double_from_FloatReg(%s) < double_from_FloatReg(%s);\n", dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_c_le_d: printf("cf = double_from_FloatReg(%s) <= double_from_FloatReg(%s);\n", dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_c_eq_d: printf("cf = double_from_FloatReg(%s) == double_from_FloatReg(%s);\n", dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_s_w: printf("%s = (int)%s;\n", fr((int)insn.instruction.GetO32_fd()), wr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_d_w: printf("%s = FloatReg_from_double((int)%s);\n", dr((int)insn.instruction.GetO32_fd()), wr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_d_s: printf("%s = FloatReg_from_double(%s);\n", dr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_s_d: printf("%s = double_from_FloatReg(%s);\n", fr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_w_d: printf("%s = cvt_w_d(double_from_FloatReg(%s));\n", wr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_w_s: printf("%s = cvt_w_s(%s);\n", wr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_cvt_l_d: case rabbitizer::InstrId::UniqueId::cpu_cvt_l_s: case rabbitizer::InstrId::UniqueId::cpu_cvt_s_l: case rabbitizer::InstrId::UniqueId::cpu_cvt_d_l: goto unimplemented; case rabbitizer::InstrId::UniqueId::cpu_cfc1: assert(insn.instruction.Get_cop1cs() == rabbitizer::Registers::Cpu::Cop1Control::COP1_CONTROL_FpcCsr); printf("%s = fcsr;\n", r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_ctc1: assert(insn.instruction.Get_cop1cs() == rabbitizer::Registers::Cpu::Cop1Control::COP1_CONTROL_FpcCsr); printf("fcsr = %s;\n", r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_div: printf("lo = (int)%s / (int)%s; ", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); printf("hi = (int)%s %% (int)%s;\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_divu: printf("lo = %s / %s; ", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); printf("hi = %s %% %s;\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_div_s: printf("%s = %s / %s;\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_div_d: printf("%s = FloatReg_from_double(double_from_FloatReg(%s) / double_from_FloatReg(%s));\n", dr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_mov_s: printf("%s = %s;\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_mov_d: printf("%s = %s;\n", dr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_mul_s: printf("%s = %s * %s;\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_mul_d: printf("%s = FloatReg_from_double(double_from_FloatReg(%s) * double_from_FloatReg(%s));\n", dr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_negu: printf("%s = -%s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_neg_s: printf("%s = -%s;\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_neg_d: printf("%s = FloatReg_from_double(-double_from_FloatReg(%s));\n", dr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_sub: if (insn.instruction.GetO32_rs() == rabbitizer::Registers::Cpu::GprO32::GPR_O32_zero) { printf("%s = -%s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt())); break; } else { goto unimplemented; } case rabbitizer::InstrId::UniqueId::cpu_sub_s: printf("%s = %s - %s;\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs()), fr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_sub_d: printf("%s = FloatReg_from_double(double_from_FloatReg(%s) - double_from_FloatReg(%s));\n", dr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs()), dr((int)insn.instruction.GetO32_ft())); break; // Jumps case rabbitizer::InstrId::UniqueId::cpu_j: dump_instr(i + 1); imm = insn.getAddress(); printf("goto L%x;\n", imm); break; case rabbitizer::InstrId::UniqueId::cpu_jal: imm = insn.getAddress(); dump_jal(i, imm); break; case rabbitizer::InstrId::UniqueId::cpu_jalr: printf("fp_dest = %s;\n", r((int)insn.instruction.GetO32_rs())); dump_instr(i + 1); printf("temp64 = trampoline(mem, sp, %s, %s, %s, %s, fp_dest);\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0), r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a1), r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a2), r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a3)); printf("%s = (uint32_t)(temp64 >> 32);\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)); printf("%s = (uint32_t)temp64;\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v1)); printf("goto L%x;\n", text_vaddr + (i + 2) * 4); label_addresses.insert(text_vaddr + (i + 2) * 4); break; case rabbitizer::InstrId::UniqueId::cpu_jr: // TODO: understand why the switch version fails, and why only it needs the nop if (insn.jtbl_addr != 0) { uint32_t jtbl_pos = insn.jtbl_addr - rodata_vaddr; assert(jtbl_pos < rodata_section_len && jtbl_pos + insn.num_cases * sizeof(uint32_t) <= rodata_section_len); #if 1 printf(";static void *const Lswitch%x[] = {\n", insn.jtbl_addr); for (uint32_t i = 0; i < insn.num_cases; i++) { uint32_t dest_addr = read_u32_be(rodata_section + jtbl_pos + i * sizeof(uint32_t)) + gp_value; printf("&&L%x,\n", dest_addr); label_addresses.insert(dest_addr); } printf("};\n"); printf("dest = Lswitch%x[%s];\n", insn.jtbl_addr, r((int)insn.index_reg)); dump_instr(i + 1); printf("goto *dest;\n"); #else // This block produces a switch instead of an array of labels. // It is not being used because currently it is a bit bugged. // It has been keep as a reference and with the main intention to fix it assert(insns[i + 1].id == MIPS_INS_NOP); printf("switch (%s) {\n", r(insn.index_reg)); for (uint32_t i = 0; i < insn.num_cases; i++) { uint32_t dest_addr = read_u32_be(rodata_section + jtbl_pos + i * sizeof(uint32_t)) + gp_value; printf("case %u: goto L%x;\n", i, dest_addr); label_addresses.insert(dest_addr); } printf("}\n"); #endif } else { if (insn.instruction.GetO32_rs() != rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { printf("UNSUPPORTED JR %s (no jumptable available)\n", r((int)insn.instruction.GetO32_rs())); } else { dump_instr(i + 1); switch (find_function(text_vaddr + i * sizeof(uint32_t))->second.nret) { case 0: printf("return;\n"); break; case 1: printf("return v0;\n"); break; case 2: printf("return ((uint64_t)v0 << 32) | v1;\n"); break; } } } break; case rabbitizer::InstrId::UniqueId::cpu_lb: imm = insn.getImmediate(); printf("%s = MEM_S8(%s + %d);\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_lbu: imm = insn.getImmediate(); printf("%s = MEM_U8(%s + %d);\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_lh: imm = insn.getImmediate(); printf("%s = MEM_S16(%s + %d);\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_lhu: imm = insn.getImmediate(); printf("%s = MEM_U16(%s + %d);\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_lui: imm = insn.getImmediate(); printf("%s = 0x%x;\n", r((int)insn.instruction.GetO32_rt()), imm << 16); break; case rabbitizer::InstrId::UniqueId::cpu_lw: imm = insn.getImmediate(); printf("%s = MEM_U32(%s + %d);\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_lwc1: imm = insn.getImmediate(); printf("%s = MEM_U32(%s + %d);\n", wr((int)insn.instruction.GetO32_ft()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_ldc1: imm = insn.getImmediate(); assert(((int)insn.instruction.GetO32_ft() - (int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0) % 2 == 0); printf("%s = MEM_U32(%s + %d);\n", wr((int)insn.instruction.GetO32_ft() + 1), r((int)insn.instruction.GetO32_rs()), imm); printf("%s = MEM_U32(%s + %d + 4);\n", wr((int)insn.instruction.GetO32_ft()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_lwl: { const char* reg = r((int)insn.instruction.GetO32_rt()); imm = insn.getImmediate(); printf("%s = %s + %d; ", reg, r((int)insn.instruction.GetO32_rs()), imm); printf("%s = ((uint32_t)MEM_U8(%s) << 24) | (MEM_U8(%s + 1) << 16) | (MEM_U8(%s + 2) << 8) | MEM_U8(%s + 3);\n", reg, reg, reg, reg, reg); } break; case rabbitizer::InstrId::UniqueId::cpu_lwr: printf("//%s\n", insn.disassemble().c_str()); break; case UniqueId_cpu_la: { uint32_t addr = insn.getAddress(); printf("%s = 0x%x;", r((int)insn.lila_dst_reg), addr); if ((text_vaddr <= addr) && (addr < text_vaddr + text_section_len)) { printf(" // function pointer"); label_addresses.insert(addr); } printf("\n"); } break; case UniqueId_cpu_li: imm = insn.getImmediate(); printf("%s = 0x%x;\n", r((int)insn.lila_dst_reg), imm); break; case rabbitizer::InstrId::UniqueId::cpu_mfc1: printf("%s = %s;\n", r((int)insn.instruction.GetO32_rt()), wr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_mfhi: printf("%s = hi;\n", r((int)insn.instruction.GetO32_rd())); break; case rabbitizer::InstrId::UniqueId::cpu_mflo: printf("%s = lo;\n", r((int)insn.instruction.GetO32_rd())); break; case rabbitizer::InstrId::UniqueId::cpu_move: printf("%s = %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs())); break; case rabbitizer::InstrId::UniqueId::cpu_mtc1: printf("%s = %s;\n", wr((int)insn.instruction.GetO32_fs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_mult: printf("lo = %s * %s;\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); printf("hi = (uint32_t)((int64_t)(int)%s * (int64_t)(int)%s >> 32);\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_multu: printf("lo = %s * %s;\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); printf("hi = (uint32_t)((uint64_t)%s * (uint64_t)%s >> 32);\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_sqrt_s: printf("%s = sqrtf(%s);\n", fr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_nor: printf("%s = ~(%s | %s);\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_not: printf("%s = ~%s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs())); break; case rabbitizer::InstrId::UniqueId::cpu_or: printf("%s = %s | %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_ori: imm = insn.getImmediate(); printf("%s = %s | 0x%x;\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_sb: imm = insn.getImmediate(); printf("MEM_U8(%s + %d) = (uint8_t)%s;\n", r((int)insn.instruction.GetO32_rs()), imm, r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_sh: imm = insn.getImmediate(); printf("MEM_U16(%s + %d) = (uint16_t)%s;\n", r((int)insn.instruction.GetO32_rs()), imm, r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_sll: printf("%s = %s << %d;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt()), insn.instruction.Get_sa()); break; case rabbitizer::InstrId::UniqueId::cpu_sllv: printf("%s = %s << (%s & 0x1f);\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs())); break; case rabbitizer::InstrId::UniqueId::cpu_slt: printf("%s = (int)%s < (int)%s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_slti: imm = insn.getImmediate(); printf("%s = (int)%s < (int)0x%x;\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_sltiu: imm = insn.getImmediate(); printf("%s = %s < 0x%x;\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_sltu: printf("%s = %s < %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_sra: printf("%s = (int)%s >> %d;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt()), insn.instruction.Get_sa()); break; case rabbitizer::InstrId::UniqueId::cpu_srav: printf("%s = (int)%s >> (%s & 0x1f);\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs())); break; case rabbitizer::InstrId::UniqueId::cpu_srl: printf("%s = %s >> %d;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt()), insn.instruction.Get_sa()); break; case rabbitizer::InstrId::UniqueId::cpu_srlv: printf("%s = %s >> (%s & 0x1f);\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs())); break; case rabbitizer::InstrId::UniqueId::cpu_subu: printf("%s = %s - %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_sw: imm = insn.getImmediate(); printf("MEM_U32(%s + %d) = %s;\n", r((int)insn.instruction.GetO32_rs()), imm, r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_swc1: imm = insn.getImmediate(); printf("MEM_U32(%s + %d) = %s;\n", r((int)insn.instruction.GetO32_rs()), imm, wr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_sdc1: assert(((int)insn.instruction.GetO32_ft() - (int)rabbitizer::Registers::Cpu::Cop1O32::COP1_O32_fv0) % 2 == 0); imm = insn.getImmediate(); printf("MEM_U32(%s + %d) = %s;\n", r((int)insn.instruction.GetO32_rs()), imm, wr((int)insn.instruction.GetO32_ft() + 1)); printf("MEM_U32(%s + %d + 4) = %s;\n", r((int)insn.instruction.GetO32_rs()), imm, wr((int)insn.instruction.GetO32_ft())); break; case rabbitizer::InstrId::UniqueId::cpu_swl: imm = insn.getImmediate(); for (int i = 0; i < 4; i++) { printf("MEM_U8(%s + %d + %d) = (uint8_t)(%s >> %d);\n", r((int)insn.instruction.GetO32_rs()), imm, i, r((int)insn.instruction.GetO32_rt()), (3 - i) * 8); } break; case rabbitizer::InstrId::UniqueId::cpu_swr: printf("//%s\n", insn.disassemble().c_str()); break; case rabbitizer::InstrId::UniqueId::cpu_trunc_w_s: printf("%s = (int)%s;\n", wr((int)insn.instruction.GetO32_fd()), fr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_trunc_w_d: printf("%s = (int)double_from_FloatReg(%s);\n", wr((int)insn.instruction.GetO32_fd()), dr((int)insn.instruction.GetO32_fs())); break; case rabbitizer::InstrId::UniqueId::cpu_trunc_l_d: case rabbitizer::InstrId::UniqueId::cpu_trunc_l_s: goto unimplemented; case rabbitizer::InstrId::UniqueId::cpu_xor: printf("%s = %s ^ %s;\n", r((int)insn.instruction.GetO32_rd()), r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt())); break; case rabbitizer::InstrId::UniqueId::cpu_xori: imm = insn.getImmediate(); printf("%s = %s ^ 0x%x;\n", r((int)insn.instruction.GetO32_rt()), r((int)insn.instruction.GetO32_rs()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_tne: imm = insn.instruction.Get_code_lower(); printf("assert(%s == %s && \"tne %d\");\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_teq: imm = insn.instruction.Get_code_lower(); printf("assert(%s != %s && \"teq %d\");\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_tge: imm = insn.instruction.Get_code_lower(); printf("assert((int)%s < (int)%s && \"tge %d\");\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_tgeu: imm = insn.instruction.Get_code_lower(); printf("assert(%s < %s && \"tgeu %d\");\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_tlt: imm = insn.instruction.Get_code_lower(); printf("assert((int)%s >= (int)%s && \"tlt %d\");\n", r((int)insn.instruction.GetO32_rs()), r((int)insn.instruction.GetO32_rt()), imm); break; case rabbitizer::InstrId::UniqueId::cpu_nop: printf("//nop;\n"); break; default: unimplemented: printf("UNIMPLEMENTED 0x%X : %s\n", insn.instruction.getRaw(), insn.disassemble().c_str()); break; } } void inspect_data_function_pointers(vector>& ret, const uint8_t* section, uint32_t section_vaddr, uint32_t len) { for (uint32_t i = 0; i < len; i += 4) { uint32_t addr = read_u32_be(section + i); if (addr == 0x430b00 || addr == 0x433b00) { // in as1, not function pointers (normal integers) continue; } if (addr == 0x4a0000) { // in copt continue; } if (section_vaddr + i >= procedure_table_start && section_vaddr + i < procedure_table_start + procedure_table_len) { // some linking table with a "all" functions, in as1 5.3 continue; } if ((addr >= text_vaddr) && (addr < text_vaddr + text_section_len) && ((addr % 4) == 0)) { #if INSPECT_FUNCTION_POINTERS fprintf(stderr, "assuming function pointer 0x%x at 0x%x\n", addr, section_vaddr + i); #endif ret.push_back(make_pair(section_vaddr + i, addr)); label_addresses.insert(addr); functions[addr].referenced_by_function_pointer = true; } } } void dump_function_signature(Function& f, uint32_t vaddr) { printf("static "); switch (f.nret) { case 0: printf("void "); break; case 1: printf("uint32_t "); break; case 2: printf("uint64_t "); break; } auto name_it = symbol_names.find(vaddr); if (name_it != symbol_names.end()) { printf("f_%s", name_it->second.c_str()); } else { printf("func_%x", vaddr); } printf("(uint8_t *mem, uint32_t sp"); if (f.v0_in) { printf(", uint32_t %s", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_v0)); } for (uint32_t i = 0; i < f.nargs; i++) { printf(", uint32_t %s", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + i)); } printf(")"); } void dump_c(void) { map symbol_names_inv; for (auto& it : symbol_names) { symbol_names_inv[it.second] = it.first; } uint32_t min_addr = UINT32_MAX; uint32_t max_addr = 0; if (data_section_len > 0) { min_addr = std::min(min_addr, data_vaddr); max_addr = std::max(max_addr, data_vaddr + data_section_len); } if (rodata_section_len > 0) { min_addr = std::min(min_addr, rodata_vaddr); max_addr = std::max(max_addr, rodata_vaddr + rodata_section_len); } if (bss_section_len) { min_addr = std::min(min_addr, bss_vaddr); max_addr = std::max(max_addr, bss_vaddr + bss_section_len); } // 64 kB. Supposedly the worst-case smallest permitted page size, increase if necessary. // Ensures the hardcoded min_addr and max_addr are sufficiently aligned for the machine running the // recompiled binaries (and not just the one doing the original recomp build). uint32_t page_size = 0x10000; min_addr = min_addr & ~(page_size - 1); max_addr = (max_addr + (page_size - 1)) & ~(page_size - 1); uint32_t stack_bottom = min_addr; min_addr -= 0x100000; // 1 MB stack stack_bottom -= 0x10; // for main's stack frame printf("#include \"header.h\"\n"); if (conservative) { printf("static uint32_t s0, s1, s2, s3, s4, s5, s6, s7, fp;\n"); } printf("static const uint32_t rodata[] = {\n"); for (size_t i = 0; i < rodata_section_len; i += 4) { printf("0x%x,%s", read_u32_be(rodata_section + i), i % 32 == 28 ? "\n" : ""); } printf("};\n"); printf("static const uint32_t data[] = {\n"); for (size_t i = 0; i < data_section_len; i += 4) { printf("0x%x,%s", read_u32_be(data_section + i), i % 32 == 28 ? "\n" : ""); } printf("};\n"); /* if (!data_function_pointers.empty()) { printf("static const struct { uint32_t orig_addr; void *recompiled_addr; } data_function_pointers[] = {\n"); for (auto item : data_function_pointers) { printf("{0x%x, &&L%x},\n", item.first, item.second); } printf("};\n"); } */ if (TRACE) { printf("static unsigned long long int cnt = 0;\n"); } for (auto& f_it : functions) { uint32_t addr = f_it.first; auto& ins = insns.at(addr_to_i(addr)); if (ins.f_livein != 0) { // Function is used dump_function_signature(f_it.second, addr); printf(";\n"); } } if (!data_function_pointers.empty() || !la_function_pointers.empty()) { printf("uint64_t trampoline(uint8_t *mem, uint32_t sp, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, " "uint32_t fp_dest) {\n"); printf("switch (fp_dest) {\n"); for (auto& it : functions) { Function& f = it.second; if (f.referenced_by_function_pointer) { printf("case 0x%x: ", it.first); if (f.nret == 1) { printf("return (uint64_t)"); } else if (f.nret == 2) { printf("return "); } auto name_it = symbol_names.find(it.first); if (name_it != symbol_names.end()) { printf("f_%s", name_it->second.c_str()); } else { printf("func_%x", it.first); } printf("(mem, sp"); for (unsigned int i = 0; i < f.nargs; i++) { printf(", a%d", i); } printf(")"); if (f.nret == 1) { printf(" << 32"); } printf(";"); if (f.nret == 0) { printf(" return 0;"); } printf("\n"); } } printf("default: abort();"); printf("}\n"); printf("}\n"); } printf("int run(uint8_t *mem, int argc, char *argv[]) {\n"); printf("mmap_initial_data_range(mem, 0x%x, 0x%x);\n", min_addr, max_addr); printf("memcpy(mem + 0x%x, rodata, 0x%x);\n", rodata_vaddr, rodata_section_len); printf("memcpy(mem + 0x%x, data, 0x%x);\n", data_vaddr, data_section_len); /* if (!data_function_pointers.empty()) { if (!LABELS_64_BIT) { printf("for (int i = 0; i < %d; i++) MEM_U32(data_function_pointers[i].orig_addr) = (uint32_t)(uintptr_t)data_function_pointers[i].recompiled_addr;\n", (int)data_function_pointers.size()); } else { printf("for (int i = 0; i < %d; i++) MEM_U32(data_function_pointers[i].orig_addr) = (uint32_t)((uintptr_t)data_function_pointers[i].recompiled_addr - (uintptr_t)&&Loffset);\n", (int)data_function_pointers.size()); } } */ printf("MEM_S32(0x%x) = argc;\n", symbol_names_inv.at("__Argc")); printf("MEM_S32(0x%x) = argc;\n", stack_bottom); printf("uint32_t al = argc * 4; for (int i = 0; i < argc; i++) al += strlen(argv[i]) + 1;\n"); printf("uint32_t arg_addr = wrapper_malloc(mem, al);\n"); printf("MEM_U32(0x%x) = arg_addr;\n", symbol_names_inv.at("__Argv")); printf("MEM_U32(0x%x) = arg_addr;\n", stack_bottom + 4); printf("uint32_t arg_strpos = arg_addr + argc * 4;\n"); printf("for (int i = 0; i < argc; i++) {MEM_U32(arg_addr + i * 4) = arg_strpos; uint32_t p = 0; do { " "MEM_S8(arg_strpos) = argv[i][p]; ++arg_strpos; } while (argv[i][p++] != '\\0');}\n"); printf("setup_libc_data(mem);\n"); // printf("gp = 0x%x;\n", gp_value); // only to recreate the outcome when ugen reads uninitialized stack memory printf("int ret = f_main(mem, 0x%x", stack_bottom); Function& main_func = functions[main_addr]; if (main_func.nargs >= 1) { printf(", argc"); } if (main_func.nargs >= 2) { printf(", arg_addr"); } printf(");\n"); if (TRACE) { printf("end: fprintf(stderr, \"cnt: %%llu\\n\", cnt);\n"); } printf("return ret;\n"); printf("}\n"); for (auto& f_it : functions) { Function& f = f_it.second; uint32_t start_addr = f_it.first; uint32_t end_addr = f.end_addr; if (insns[addr_to_i(start_addr)].f_livein == 0) { // Non-used function, skip continue; } printf("\n"); dump_function_signature(f, start_addr); printf(" {\n"); printf("const uint32_t zero = 0;\n"); if (!conservative) { printf("uint32_t at = 0, v1 = 0, t0 = 0, t1 = 0, t2 = 0,\n"); printf("t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0,\n"); printf("s6 = 0, s7 = 0, t8 = 0, t9 = 0, gp = 0, fp = 0, s8 = 0, ra = 0;\n"); } else { printf("uint32_t at = 0, v1 = 0, t0 = 0, t1 = 0, t2 = 0,\n"); printf("t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, t8 = 0, t9 = 0, gp = 0x10000, ra = 0x10000;\n"); } printf("uint32_t lo = 0, hi = 0;\n"); printf("int cf = 0;\n"); printf("uint64_t temp64;\n"); printf("double tempf64;\n"); printf("uint32_t fp_dest;\n"); printf("void *dest;\n"); if (!f.v0_in) { printf("uint32_t v0 = 0;\n"); } for (uint32_t j = f.nargs; j < 4; j++) { printf("uint32_t %s = 0;\n", r((int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_a0 + j)); } for (size_t i = addr_to_i(start_addr), end_i = addr_to_i(end_addr); i < end_i; i++) { Insn& insn = insns[i]; uint32_t vaddr = text_vaddr + i * 4; if (label_addresses.count(vaddr)) { printf("L%x:\n", vaddr); } #if DUMP_INSTRUCTIONS printf("// %s:\n", insn.disassemble().c_str()); #endif dump_instr(i); } printf("}\n"); } /* for (size_t i = 0; i < insns.size(); i++) { Insn& insn = insns[i]; uint32_t vaddr = text_vaddr + i * 4; auto fn_it = functions.find(vaddr); if (fn_it != functions.end()) { Function& f = fn_it->second; printf("}\n\n"); switch (f.nret) { case 0: printf("void "); break; case 1: printf("uint32_t "); break; case 2: printf("uint64_t "); break; } auto name_it = symbol_names.find(vaddr); if (name_it != symbol_names.end()) { printf("%s", name_it->second.c_str()); } else { printf("func_%x", vaddr); } printf("(uint8_t *mem, uint32_t sp"); if (f.v0_in) { printf(", uint32_t %s", r(MIPS_REG_V0)); } for (uint32_t i = 0; i < f.nargs; i++) { printf(", uint32_t %s", r(MIPS_REG_A0 + i)); } printf(") {\n"); printf("const uint32_t zero = 0;\n"); printf("uint32_t at = 0, v1 = 0, t0 = 0, t1 = 0, t2 = 0,\n"); printf("t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0,\n"); printf("s6 = 0, s7 = 0, t8 = 0, t9 = 0, gp = 0, fp = 0, s8 = 0, ra = 0;\n"); printf("uint32_t lo = 0, hi = 0;\n"); printf("int cf = 0;\n"); if (!f.v0_in) { printf("uint32_t v0 = 0;\n"); } for (uint32_t j = f.nargs; j < 4; j++) { printf("uint32_t %s = 0;\n", r(MIPS_REG_A0 + j)); } } if (label_addresses.count(vaddr)) { printf("L%x:\n", vaddr); } dump_instr(i); } */ } void parse_elf(const uint8_t* data, size_t file_len) { Elf32_Ehdr* ehdr; Elf32_Shdr *shdr, *str_shdr, *sym_shdr = NULL, *dynsym_shdr, *dynamic_shdr, *reginfo_shdr, *got_shdr, *sym_strtab = NULL, *sym_dynstr; int text_section_index = -1; int symtab_section_index = -1; int dynsym_section_index = -1; int reginfo_section_index = -1; int dynamic_section_index = -1; int got_section_index = -1; int rodata_section_index = -1; int data_section_index = -1; int bss_section_index = -1; uint32_t text_offset = 0; uint32_t vaddr_adj = 0; if (file_len < 4 || data[0] != 0x7f || data[1] != 'E' || data[2] != 'L' || data[3] != 'F') { fprintf(stderr, "Not an ELF file.\n"); exit(EXIT_FAILURE); } ehdr = (Elf32_Ehdr*)data; if (ehdr->e_ident[EI_DATA] != 2 || u16be(ehdr->e_machine) != 8) { fprintf(stderr, "Not big-endian MIPS.\n"); exit(EXIT_FAILURE); } if (u16be(ehdr->e_shstrndx) == 0) { // (We could look at program headers instead in this case.) fprintf(stderr, "Missing section headers; stripped binaries are not yet supported.\n"); exit(EXIT_FAILURE); } #define SECTION(index) (Elf32_Shdr*)(data + u32be(ehdr->e_shoff) + (index)*u16be(ehdr->e_shentsize)) #define STR(strtab, offset) (const char*)(data + u32be(strtab->sh_offset) + offset) str_shdr = SECTION(u16be(ehdr->e_shstrndx)); for (int i = 0; i < u16be(ehdr->e_shnum); i++) { shdr = SECTION(i); const char* name = STR(str_shdr, u32be(shdr->sh_name)); if (strcmp(name, ".text") == 0) { text_offset = u32be(shdr->sh_offset); text_vaddr = u32be(shdr->sh_addr); vaddr_adj = text_vaddr - u32be(shdr->sh_addr); text_section_len = u32be(shdr->sh_size); text_section = data + text_offset; text_section_index = i; } if (u32be(shdr->sh_type) == SHT_SYMTAB) { symtab_section_index = i; } if (u32be(shdr->sh_type) == SHT_DYNSYM) { dynsym_section_index = i; } if (u32be(shdr->sh_type) == SHT_MIPS_REGINFO) { reginfo_section_index = i; } if (u32be(shdr->sh_type) == SHT_DYNAMIC) { dynamic_section_index = i; } if (strcmp(name, ".got") == 0) { got_section_index = i; } if (strcmp(name, ".rodata") == 0) { rodata_section_index = i; } if (strcmp(name, ".data") == 0) { data_section_index = i; } if (strcmp(name, ".bss") == 0) { bss_section_index = i; } } if (text_section_index == -1) { fprintf(stderr, "Missing .text section.\n"); exit(EXIT_FAILURE); } if (symtab_section_index == -1 && dynsym_section_index == -1) { fprintf(stderr, "Missing .symtab or .dynsym section.\n"); exit(EXIT_FAILURE); } if (dynsym_section_index != -1) { if (reginfo_section_index == -1) { fprintf(stderr, "Missing .reginfo section.\n"); exit(EXIT_FAILURE); } if (dynamic_section_index == -1) { fprintf(stderr, "Missing .dynamic section.\n"); exit(EXIT_FAILURE); } if (got_section_index == -1) { fprintf(stderr, "Missing .got section.\n"); exit(EXIT_FAILURE); } } if (rodata_section_index != -1) { shdr = SECTION(rodata_section_index); uint32_t size = u32be(shdr->sh_size); rodata_section = data + u32be(shdr->sh_offset); rodata_section_len = size; rodata_vaddr = u32be(shdr->sh_addr); } if (data_section_index != -1) { shdr = SECTION(data_section_index); uint32_t size = u32be(shdr->sh_size); data_section = data + u32be(shdr->sh_offset); data_section_len = size; data_vaddr = u32be(shdr->sh_addr); } if (bss_section_index != -1) { shdr = SECTION(bss_section_index); uint32_t size = u32be(shdr->sh_size); bss_section_len = size; bss_vaddr = u32be(shdr->sh_addr); } // add symbols if (symtab_section_index != -1) { sym_shdr = SECTION(symtab_section_index); sym_strtab = SECTION(u32be(sym_shdr->sh_link)); assert(0 && ".symtab not supported - use a program with .dynsym instead"); assert(u32be(sym_shdr->sh_entsize) == sizeof(Elf32_Sym)); for (uint32_t i = 0; i < u32be(sym_shdr->sh_size); i += sizeof(Elf32_Sym)) { Elf32_Sym* sym = (Elf32_Sym*)(data + u32be(sym_shdr->sh_offset) + i); const char* name = STR(sym_strtab, u32be(sym->st_name)); uint32_t addr = u32be(sym->st_value); if (u16be(sym->st_shndx) != text_section_index || name[0] == '.') { continue; } addr += vaddr_adj; // disasm_label_add(state, name, addr, u32be(sym->st_size), true); } } if (dynsym_section_index != -1) { dynsym_shdr = SECTION(dynsym_section_index); sym_dynstr = SECTION(u32be(dynsym_shdr->sh_link)); reginfo_shdr = SECTION(reginfo_section_index); dynamic_shdr = SECTION(dynamic_section_index); got_shdr = SECTION(got_section_index); Elf32_RegInfo* reg_info = (Elf32_RegInfo*)(data + u32be(reginfo_shdr->sh_offset)); uint32_t gp_base = u32be(reg_info->ri_gp_value); // gp should have this value through the program run uint32_t got_start = 0; uint32_t local_got_no = 0; uint32_t first_got_sym = 0; uint32_t dynsym_no = 0; // section size can't be used due to alignment 16 padding assert(u32be(dynamic_shdr->sh_entsize) == sizeof(Elf32_Dyn)); for (uint32_t i = 0; i < u32be(dynamic_shdr->sh_size); i += sizeof(Elf32_Dyn)) { Elf32_Dyn* dyn = (Elf32_Dyn*)(data + u32be(dynamic_shdr->sh_offset) + i); if (u32be(dyn->d_tag) == DT_PLTGOT) { got_start = u32be(dyn->d_un.d_ptr); } if (u32be(dyn->d_tag) == DT_MIPS_LOCAL_GOTNO) { local_got_no = u32be(dyn->d_un.d_val); } if (u32be(dyn->d_tag) == DT_MIPS_GOTSYM) { first_got_sym = u32be(dyn->d_un.d_val); } if (u32be(dyn->d_tag) == DT_MIPS_SYMTABNO) { dynsym_no = u32be(dyn->d_un.d_val); } } assert(got_start != 0); // value to add to asm gp offset, for example 32752, if -32752(gp) refers to the first entry in got. uint32_t gp_adj = gp_base - got_start; assert(gp_adj < 0x10000); assert(u32be(dynsym_shdr->sh_entsize) == sizeof(Elf32_Sym)); uint32_t global_got_no = dynsym_no - first_got_sym; // global_got_entry *global_entries = (global_got_entry *)calloc(global_got_no, sizeof(global_got_entry)); got_globals.resize(global_got_no); uint32_t common_start = ~0U; vector common_order; for (uint32_t i = 0; i < dynsym_no; i++) { Elf32_Sym* sym = (Elf32_Sym*)(data + u32be(dynsym_shdr->sh_offset) + i * sizeof(Elf32_Sym)); const char* name = STR(sym_dynstr, u32be(sym->st_name)); uint32_t addr = u32be(sym->st_value); addr += vaddr_adj; uint8_t type = ELF32_ST_TYPE(sym->st_info); if (!strcmp(name, "_procedure_table")) { procedure_table_start = addr; } else if (!strcmp(name, "_procedure_table_size")) { procedure_table_len = 40 * u32be(sym->st_value); } if ((u16be(sym->st_shndx) == SHN_MIPS_TEXT && type == STT_FUNC) || (type == STT_OBJECT && (u16be(sym->st_shndx) == SHN_MIPS_ACOMMON || u16be(sym->st_shndx) == SHN_MIPS_DATA))) { // disasm_label_add(state, name, addr, u32be(sym->st_size), true); if (type == STT_OBJECT) {} if (u16be(sym->st_shndx) == SHN_MIPS_ACOMMON) { if (addr < common_start) { common_start = addr; } common_order.push_back(name); } if (type == STT_FUNC) { add_function(addr); if (strcmp(name, "main") == 0) { main_addr = addr; } if (strcmp(name, "_mcount") == 0) { mcount_addr = addr; } symbol_names[addr] = name; } } if (i >= first_got_sym) { uint32_t got_value = u32be(*(uint32_t*)(data + u32be(got_shdr->sh_offset) + (local_got_no + (i - first_got_sym)) * sizeof(uint32_t))); if (u16be(sym->st_shndx) == SHN_MIPS_TEXT && type == STT_FUNC) { // got_globals[i - first_got_sym] = got_value; // label_addresses.insert(got_value); got_globals[i - first_got_sym] = addr; // to include the 3 instr gp header thing label_addresses.insert(addr); } else if (type == STT_OBJECT && (u16be(sym->st_shndx) == SHN_UNDEF || u16be(sym->st_shndx) == SHN_COMMON)) { // symbol defined externally (for example in libc) got_globals[i - first_got_sym] = got_value; } else { got_globals[i - first_got_sym] = addr; } symbol_names[got_globals[i - first_got_sym]] = name; } } uint32_t* local_entries = (uint32_t*)calloc(local_got_no, sizeof(uint32_t)); got_locals.resize(local_got_no); for (uint32_t i = 0; i < local_got_no; i++) { uint32_t* entry = (uint32_t*)(data + u32be(got_shdr->sh_offset) + i * sizeof(uint32_t)); got_locals[i] = u32be(*entry); } gp_value = gp_base; gp_value_adj = gp_adj; free(local_entries); } // add relocations for (int i = 0; i < u16be(ehdr->e_shnum); i++) { Elf32_Rel* prevHi = NULL; shdr = SECTION(i); if (u32be(shdr->sh_type) != SHT_REL || u32be(shdr->sh_info) != (uint32_t)text_section_index) continue; if (sym_shdr == NULL) { fprintf(stderr, "Relocations without .symtab section\n"); exit(EXIT_FAILURE); } assert(u32be(shdr->sh_link) == (uint32_t)symtab_section_index); assert(u32be(shdr->sh_entsize) == sizeof(Elf32_Rel)); for (uint32_t i = 0; i < u32be(shdr->sh_size); i += sizeof(Elf32_Rel)) { Elf32_Rel* rel = (Elf32_Rel*)(data + u32be(shdr->sh_offset) + i); uint32_t offset = text_offset + u32be(rel->r_offset); uint32_t symIndex = ELF32_R_SYM(u32be(rel->r_info)); uint32_t rtype = ELF32_R_TYPE(u32be(rel->r_info)); const char* symName = "0"; if (symIndex != STN_UNDEF) { Elf32_Sym* sym = (Elf32_Sym*)(data + u32be(sym_shdr->sh_offset) + symIndex * sizeof(Elf32_Sym)); symName = STR(sym_strtab, u32be(sym->st_name)); } if (rtype == R_MIPS_HI16) { if (prevHi != NULL) { fprintf(stderr, "Consecutive R_MIPS_HI16.\n"); exit(EXIT_FAILURE); } prevHi = rel; continue; } if (rtype == R_MIPS_LO16) { int32_t addend = (int16_t)((data[offset + 2] << 8) + data[offset + 3]); if (prevHi != NULL) { uint32_t offset2 = text_offset + u32be(prevHi->r_offset); addend += (uint32_t)((data[offset2 + 2] << 8) + data[offset2 + 3]) << 16; // add_reloc(state, offset2, symName, addend, out_range.vaddr); } prevHi = NULL; // add_reloc(state, offset, symName, addend, out_range.vaddr); } else if (rtype == R_MIPS_26) { int32_t addend = (u32be(*(uint32_t*)(data + offset)) & ((1 << 26) - 1)) << 2; if (addend >= (1 << 27)) { addend -= 1 << 28; } // add_reloc(state, offset, symName, addend, out_range.vaddr); } else { fprintf(stderr, "Bad relocation type %d.\n", rtype); exit(EXIT_FAILURE); } } if (prevHi != NULL) { fprintf(stderr, "R_MIPS_HI16 without matching R_MIPS_LO16.\n"); exit(EXIT_FAILURE); } } } #undef SECTION #undef STR size_t read_file(const char* file_name, uint8_t** data) { FILE* in; uint8_t* in_buf = NULL; long file_size; long bytes_read; in = fopen(file_name, "rb"); assert(in != nullptr); // allocate buffer to read from offset to end of file fseek(in, 0, SEEK_END); file_size = ftell(in); assert(file_size != -1L); in_buf = (uint8_t*)malloc(file_size); fseek(in, 0, SEEK_SET); // read bytes bytes_read = fread(in_buf, 1, file_size, in); assert(bytes_read == file_size); fclose(in); *data = in_buf; return bytes_read; } #ifdef UNIX_PLATFORM void crashHandler(int sig) { void* array[4096]; const size_t nMaxFrames = std::size(array); size_t size = backtrace(array, nMaxFrames); char** symbols = backtrace_symbols(array, nMaxFrames); fprintf(stderr, "\n recomp crashed. (Signal: %i)\n", sig); // Feel free to add more crash messages. const char* crashEasterEgg[] = { "\tIT'S A SECRET TO EVERYBODY. \n\tBut it shouldn't be, you'd better ask about it!", "\tI AM ERROR.", "\tGRUMBLE,GRUMBLE...", "\tDODONGO DISLIKES SMOKE \n\tAnd recomp dislikes whatever you fed it.", "\tMay the way of the Hero lead \n\tto the debugger.", "\tTHE WIND FISH SLUMBERS LONG... \n\tTHE HERO'S LIFE GONE... ", "\tSEA BEARS FOAM, SLEEP BEARS DREAMS. \n\tBOTH END IN THE SAME WAY CRASSSH!", "\tYou've met with a terrible fate, haven't you?", "\tMaster, I calculate a 100% probability that recomp has crashed. \n\tAdditionally, the " "batteries in your Wii Remote are nearly depleted.", "\t CONGRATURATIONS! \n" "\tAll Pages are displayed.\n" "\t THANK YOU! \n" "\t You are great debugger!", "\tRCP is HUNG UP!!\n" "\tOh! MY GOD!!", }; srand(time(nullptr)); auto easterIndex = rand() % std::size(crashEasterEgg); fprintf(stderr, "\n%s\n\n", crashEasterEgg[easterIndex]); fprintf(stderr, "Traceback:\n"); for (size_t i = 1; i < size; i++) { Dl_info info; uint32_t gotAddress = dladdr(array[i], &info); std::string functionName(symbols[i]); if (gotAddress != 0 && info.dli_sname != nullptr) { int32_t status; char* demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status); const char* nameFound = info.dli_sname; if (status == 0) { nameFound = demangled; } { char auxBuffer[0x8000]; snprintf(auxBuffer, std::size(auxBuffer), "%s (+0x%lX)", nameFound, (char*)array[i] - (char*)info.dli_saddr); functionName = auxBuffer; } free(demangled); #if FULL_TRACEBACK == 0 fprintf(stderr, "%-3zd %s\n", i, functionName.c_str()); #endif } #if FULL_TRACEBACK != 0 fprintf(stderr, "%-3zd %s\n", i, functionName.c_str()); #endif } fprintf(stderr, "\n"); free(symbols); exit(1); } #endif int main(int argc, char* argv[]) { const char* filename = argv[1]; if (strcmp(filename, "--conservative") == 0) { conservative = true; filename = argv[2]; } #ifdef UNIX_PLATFORM signal(SIGSEGV, crashHandler); signal(SIGABRT, crashHandler); #endif uint8_t* data; size_t len = read_file(filename, &data); parse_elf(data, len); disassemble(); inspect_data_function_pointers(data_function_pointers, rodata_section, rodata_vaddr, rodata_section_len); inspect_data_function_pointers(data_function_pointers, data_section, data_vaddr, data_section_len); pass1(); pass2(); pass3(); pass4(); pass5(); pass6(); // dump(); dump_c(); free(data); return 0; }