sm64/tools/ido5.3_recomp/recomp.cpp
2021-07-12 23:17:54 -04:00

2939 lines
116 KiB
C++

#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <capstone.h>
#include "elf.h"
#define INSPECT_FUNCTION_POINTERS 0 // set this to 1 when testing a new program, to verify that no false function pointers are found
#ifndef TRACE
#define TRACE 0
#endif
#define LABELS_64_BIT 1
#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]))
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 {
uint32_t id;
uint8_t op_count;
string mnemonic;
string op_str;
cs_mips_op operands[8];
uint8_t is_jump: 1;
uint8_t is_global_got_memop: 1;
uint8_t no_following_successor: 1;
int linked_insn;
union {
uint32_t linked_value;
float linked_float;
};
uint32_t jtbl_addr;
uint32_t num_cases;
mips_reg index_reg;
vector<Edge> successors;
vector<Edge> predecessors;
uint64_t b_liveout;
uint64_t b_livein;
uint64_t f_livein;
uint64_t f_liveout;
};
struct Function {
vector<uint32_t> 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;
};
static bool conservative;
static csh handle;
static const uint8_t *text_section;
static uint32_t text_section_len;
static uint32_t text_vaddr;
static const uint8_t *rodata_section;
static uint32_t rodata_section_len;
static uint32_t rodata_vaddr;
static const uint8_t *data_section;
static uint32_t data_section_len;
static uint32_t data_vaddr;
static uint32_t bss_section_len;
static uint32_t bss_vaddr;
static vector<Insn> insns;
static set<uint32_t> label_addresses;
static vector<uint32_t> got_globals;
static vector<uint32_t> got_locals;
static uint32_t gp_value;
static uint32_t gp_value_adj;
static map<uint32_t, string> symbol_names;
static vector<pair<uint32_t, uint32_t>> data_function_pointers;
static set<uint32_t> li_function_pointers;
static map<uint32_t, Function> functions;
static uint32_t main_addr;
static uint32_t mcount_addr;
static uint32_t procedure_table_start;
static uint32_t procedure_table_len;
#define FLAG_NO_MEM 1
#define FLAG_VARARG 2
static const struct {
const char *name;
const char *params;
int flags;
} extern_functions[] = {
{"exit", "vi"}, // override exit from application
{"abort", "v"},
{"sbrk", "pi"},
{"malloc", "pu"},
{"calloc", "puu"},
{"realloc", "ppu"},
{"free", "vp"},
{"fscanf", "ipp", FLAG_VARARG},
{"printf", "ip", FLAG_VARARG},
{"sprintf", "ipp", FLAG_VARARG},
{"fprintf", "ipp", FLAG_VARARG},
{"_doprnt", "ippp"},
{"strlen", "up"},
{"open", "ipii"},
{"creat", "ipi"},
{"access", "ipi"},
{"rename", "ipp"},
{"utime", "ipp"},
{"flock", "iii"},
{"chmod", "ipu"},
{"umask", "ii", FLAG_NO_MEM},
{"ecvt", "pdipp"},
{"fcvt", "pdipp"},
{"sqrt", "dd", FLAG_NO_MEM},
{"sqrtf", "ff", FLAG_NO_MEM},
{"atoi", "ip"},
{"atol", "ip"},
{"atof", "dp"},
{"strtol", "ippi"},
{"strtoul", "uppi"},
{"strtod", "dpp"},
{"strchr", "ppi"},
{"strrchr", "ppi"},
{"strcspn", "upp"},
{"strpbrk", "ppp"},
{"fstat", "iip"},
{"stat", "ipp"},
{"ftruncate", "iii"},
{"bcopy", "vppu"},
{"memcpy", "pppu"},
{"memccpy", "pppiu"},
{"read", "iipu"},
{"write", "iipu"},
{"fopen", "ppp"},
{"freopen", "pppp"},
{"fclose", "ip"},
{"ftell", "ip"},
{"rewind", "vp"},
{"fseek", "ipii"},
{"lseek", "iiii"},
{"fflush", "ip"},
{"dup", "ii"},
{"dup2", "iii"},
{"pipe", "ip"},
{"perror", "vp"},
{"fdopen", "iip"},
{"memset", "ppiu"},
{"bcmp", "ippu"},
{"memcmp", "ippu"},
{"getpid", "i", FLAG_NO_MEM},
{"getpgrp", "i"},
{"remove", "ip"},
{"unlink", "ip"},
{"close", "ii"},
{"strcmp", "ipp"},
{"strncmp", "ippu"},
{"strcpy", "ppp"},
{"strncpy", "pppu"},
{"strcat", "ppp"},
{"strncat", "pppu"},
{"strtok", "ppp"},
{"strstr", "ppp"},
{"strdup", "pp"},
{"toupper", "ii", FLAG_NO_MEM},
{"tolower", "ii", FLAG_NO_MEM},
{"gethostname", "ipu"},
{"isatty", "ii"},
{"strftime", "upupp"},
{"times", "ip"},
{"clock", "i", FLAG_NO_MEM},
{"ctime", "pp"},
{"localtime", "pp"},
{"setvbuf", "ippiu"},
{"__semgetc", "ip"},
{"__semputc", "iip"},
{"fgetc", "ip"},
{"fgets", "ipip"},
{"__filbuf", "ip"},
{"__flsbuf", "iip"},
{"ungetc", "iip"},
{"gets", "pp"},
{"fread", "upuup"},
{"fwrite", "upuup"},
{"fputs", "ipp"},
{"puts", "ip"},
{"getcwd", "ppu"},
{"time", "ip"},
{"bzero", "vpu"},
{"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"},
{"_cleanup", "v"},
{"_rld_new_interface", "pu", FLAG_VARARG},
{"_exithandle", "v"},
{"_prctl", "ii", FLAG_VARARG},
{"_atod", "dpii"},
{"pathconf", "ipi"},
{"getenv", "pp"},
{"gettxt", "ppp"},
{"setlocale", "pip"},
{"mmap", "ppuiiii"},
{"munmap", "ipu"},
{"mprotect", "ipui"},
{"sysconf", "ii"},
{"getpagesize", "i"},
{"strerror", "pi"},
{"ioctl", "iiu", FLAG_VARARG},
{"fcntl", "iii", FLAG_VARARG},
{"signal", "pit"},
{"sigset", "pit"},
{"get_fpc_csr", "i"},
{"set_fpc_csr", "ii"},
{"setjmp", "ip"},
{"longjmp", "vpi"},
{"tempnam", "ppp"},
{"tmpnam", "pp"},
{"mktemp", "pp"},
{"mkstemp", "ip"},
{"tmpfile", "p"},
{"wait", "ip"},
{"kill", "iii"},
{"execlp", "ip", FLAG_VARARG},
{"execv", "ipp"},
{"execvp", "ipp"},
{"fork", "i"},
{"system", "ip"},
{"tsearch", "pppp"},
{"tfind", "pppp"},
{"qsort", "vpuut"},
{"regcmp", "pp", FLAG_VARARG},
{"regex", "ppp", FLAG_VARARG},
{"__assert", "vppi"},
};
static void disassemble(void) {
csh handle;
cs_insn *disasm;
size_t disasm_size = 0;
assert(cs_open(CS_ARCH_MIPS, (cs_mode)(CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN), &handle) == CS_ERR_OK);
cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
insns.reserve(1 + text_section_len / sizeof(uint32_t)); // +1 for dummy instruction
while (text_section_len > disasm_size * sizeof(uint32_t)) {
size_t disasm_len = disasm_size * sizeof(uint32_t);
size_t remaining = text_section_len - disasm_len;
size_t current_len = std::min<size_t>(remaining, 1024);
size_t cur_disasm_size = cs_disasm(handle, &text_section[disasm_len], current_len, text_vaddr + disasm_len, 0, &disasm);
disasm_size += cur_disasm_size;
for (size_t i = 0; i < cur_disasm_size; i++) {
insns.push_back(Insn());
Insn& insn = insns.back();
insn.id = disasm[i].id;
insn.mnemonic = disasm[i].mnemonic;
insn.op_str = disasm[i].op_str;
if (disasm[i].detail != nullptr && disasm[i].detail->mips.op_count > 0) {
insn.op_count = disasm[i].detail->mips.op_count;
memcpy(insn.operands, disasm[i].detail->mips.operands, sizeof(insn.operands));
}
insn.is_jump = cs_insn_group(handle, &disasm[i], MIPS_GRP_JUMP) || insn.id == MIPS_INS_JAL || insn.id == MIPS_INS_BAL || insn.id == MIPS_INS_JALR;
insn.linked_insn = -1;
}
cs_free(disasm, cur_disasm_size);
}
cs_close(&handle);
{
// Add dummy instruction to avoid out of bounds
insns.push_back(Insn());
Insn& insn = insns.back();
insn.id = MIPS_INS_NOP;
insn.mnemonic = "nop";
insn.no_following_successor = true;
}
}
static void add_function(uint32_t addr) {
if (addr >= text_vaddr && addr < text_vaddr + text_section_len) {
functions[addr];
}
}
static map<uint32_t, Function>::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;
}
// try to find a matching LUI for a given register
static void link_with_lui(int offset, uint32_t 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--) {
// use an `if` instead of `case` block to allow breaking out of the `for` loop
if (insns[search].id == MIPS_INS_LUI) {
uint32_t rd = insns[search].operands[0].reg;
if (reg == rd) {
break;
}
} else if (insns[search].id == MIPS_INS_LW ||
insns[search].id == MIPS_INS_LD ||
insns[search].id == MIPS_INS_ADDIU ||
//insns[search].id == MIPS_INS_ADDU || used in jump tables for offset
insns[search].id == MIPS_INS_ADD ||
insns[search].id == MIPS_INS_SUB ||
insns[search].id == MIPS_INS_SUBU) {
uint32_t rd = insns[search].operands[0].reg;
if (reg == rd) {
if (insns[search].id == MIPS_INS_LW && insns[search].operands[1].mem.base == MIPS_REG_GP) {
int mem_imm0 = (int)insns[search].operands[1].mem.disp;
uint32_t got_entry = (mem_imm0 + gp_value_adj) / sizeof(uint32_t);
if (got_entry < got_locals.size()) {
// used for static functions
char buf[32];
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;
//vaddr_references[addr].insert(text_vaddr + offset * 4);
insns[search].id = MIPS_INS_LI;
insns[search].mnemonic = "li";
sprintf(buf, "$%s, 0x%x", cs_reg_name(handle, rd), addr);
insns[search].op_str = buf;
insns[search].operands[1].type = MIPS_OP_IMM;
insns[search].operands[1].imm = addr;
switch (insns[offset].id) {
case MIPS_INS_ADDIU:
insns[offset].id = MIPS_INS_MOVE;
insns[offset].operands[1].type = MIPS_OP_REG;
insns[offset].mnemonic = "move";
sprintf(buf, "$%s, $%s", cs_reg_name(handle, insns[offset].operands[0].reg), cs_reg_name(handle, rd));
insns[offset].op_str = buf;
if (addr >= text_vaddr && addr < text_vaddr + text_section_len) {
add_function(addr);
}
break;
case MIPS_INS_LB:
case MIPS_INS_LBU:
case MIPS_INS_SB:
case MIPS_INS_LH:
case MIPS_INS_LHU:
case MIPS_INS_SH:
case MIPS_INS_LW:
case MIPS_INS_SW:
case MIPS_INS_LDC1:
case MIPS_INS_LWC1:
case MIPS_INS_SWC1:
insns[offset].operands[1].mem.disp = 0;
sprintf(buf, "$%s, ($%s)", cs_reg_name(handle, insns[offset].operands[0].reg), cs_reg_name(handle, rd));
insns[offset].op_str = buf;
break;
default:
assert(0);
}
}
break;
} else {
// ignore: reg is pointer, offset is probably struct data member
break;
}
}
} else if (insns[search].id == MIPS_INS_JR &&
insns[search].operands[0].reg == MIPS_REG_RA && offset - search >= 2) {
// stop looking when previous `jr ra` is hit,
// but ignore if `offset` is branch delay slot for this `jr ra`
break;
}
}
}
// for a given `jalr t9`, find the matching t9 load
static 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 (insns[search].operands[0].reg == MIPS_REG_T9) {
if (insns[search].id == MIPS_INS_LW || insns[search].id == MIPS_INS_LI) {
if (insns[search].is_global_got_memop || insns[search].id == MIPS_INS_LI) {
char buf[32];
sprintf(buf, "0x%x", insns[search].linked_value);
insns[search].linked_insn = offset;
insns[offset].linked_insn = search;
insns[offset].linked_value = insns[search].linked_value;
//insns[offset].label = insns[search].label;
//function_entry_points.insert(insns[search].linked_value);
insns[offset].id = MIPS_INS_JAL;
insns[offset].mnemonic = "jal";
insns[offset].op_str = buf;
insns[offset].operands[0].type = MIPS_OP_IMM;
insns[offset].operands[0].imm = insns[search].linked_value;
insns[search].id = MIPS_INS_NOP;
insns[search].mnemonic = "nop";
insns[search].op_str = "";
insns[search].is_global_got_memop = false;
add_function(insns[search].linked_value);
}
break;
} else if (insns[search].id == MIPS_INS_ADDIU) {
if (insns[search].linked_insn != -1) {
//function_entry_points.insert(insns[search].linked_value);
uint32_t first = insns[search].linked_insn;
insns[search].linked_insn = offset;
insns[offset].linked_insn = first;
insns[offset].linked_value = insns[search].linked_value;
}
break;
} else if (insns[search].id == MIPS_INS_LI) {
if (insns[search].linked_insn != -1) {
//function_entry_points.insert(insns[search].linked_value);
uint32_t first = insns[search].linked_insn;
insns[search].linked_insn = offset;
insns[offset].linked_insn = first;
insns[offset].linked_value = insns[search].linked_value;
insns[search].id = MIPS_INS_NOP;
insns[search].mnemonic = "nop";
insns[search].op_str = "";
}
break;
} else if (insns[search].id == MIPS_INS_LD ||
insns[search].id == MIPS_INS_ADDU ||
insns[search].id == MIPS_INS_ADD ||
insns[search].id == MIPS_INS_SUB ||
insns[search].id == MIPS_INS_SUBU) {
break;
}
} else if (insns[search].id == MIPS_INS_JR &&
insns[search].operands[0].reg == MIPS_REG_RA)
{
// stop looking when previous `jr ra` is hit
break;
}
}
}
static void pass1(void) {
for (size_t i = 0; i < insns.size(); i++) {
Insn& insn = insns[i];
if (insn.id == MIPS_INS_BAL) {
insn.id = MIPS_INS_JAL;
insn.mnemonic = "jal";
}
if (insn.is_jump) {
if (insn.id == MIPS_INS_JAL || insn.id == MIPS_INS_J) {
uint32_t target = (uint32_t)insn.operands[0].imm;
label_addresses.insert(target);
add_function(target);
} else if (insn.id == MIPS_INS_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].id == MIPS_INS_ADDU && insns[i - 1].operands[2].reg == MIPS_REG_GP;
bool has_nop = insns[i - is_pic - 1].id == MIPS_INS_NOP;
bool has_extra = insns[i - is_pic - has_nop - 5].id != MIPS_INS_BEQZ;
int lw = i - is_pic - has_nop - 1;
if (insns[lw].id != MIPS_INS_LW) {
--lw;
}
if (insns[lw].id == MIPS_INS_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].id != MIPS_INS_ADDU) {
--addu_index;
}
mips_reg index_reg = (mips_reg)insns[addu_index - 1].operands[1].reg;
if (insns[addu_index].id != MIPS_INS_ADDU) {
goto skip;
}
if (insns[addu_index - 1].id != MIPS_INS_SLL) {
goto skip;
}
if (insns[addu_index - 1].operands[0].reg != insn.operands[0].reg) {
goto skip;
}
for (int j = 3; j <= 4; j++) {
if (insns[lw - j].id == MIPS_INS_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].id == MIPS_INS_SLTIU &&
insns[lw - has_extra - j].operands[0].reg == MIPS_REG_AT)
{
sltiu_index = j;
break;
}
if (insns[lw - has_extra - j].id == MIPS_INS_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].id == MIPS_INS_SLTIU) {
num_cases = insns[lw - has_extra - sltiu_index].operands[2].imm;
found = true;
} else if (andi_index != -1) {
num_cases = insns[andi_index].operands[2].imm + 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].id = MIPS_INS_NOP;
}
//printf("jump table at %08x, size %u\n", jtbl_addr, num_cases);
insn.jtbl_addr = jtbl_addr;
insn.num_cases = num_cases;
insn.index_reg = index_reg;
insns[lw].id = MIPS_INS_NOP;
insns[addu_index].id = MIPS_INS_NOP;
insns[addu_index - 1].id = MIPS_INS_NOP;
if (!and_variant) {
insns[addu_index - 2].id = MIPS_INS_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 {
for (int j = 0; j < insn.op_count; j++) {
if (insn.operands[j].type == MIPS_OP_IMM) {
uint32_t target = (uint32_t)insn.operands[j].imm;
label_addresses.insert(target);
}
}
}
}
switch (insns[i].id) {
// find floating point LI
case MIPS_INS_MTC1:
{
unsigned int rt = insns[i].operands[0].reg;
for (int s = i - 1; s >= 0; s--) {
if (insns[s].id == MIPS_INS_LUI && insns[s].operands[0].reg == rt) {
float f;
uint32_t lui_imm = (uint32_t)(insns[s].operands[1].imm << 16);
memcpy(&f, &lui_imm, sizeof(f));
insns[s].operands[1].imm <<= 16;
// 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].id = MIPS_INS_LI;
insns[s].mnemonic = "li";
break;
} else if (insns[s].id == MIPS_INS_LW ||
insns[s].id == MIPS_INS_LD ||
insns[s].id == MIPS_INS_LH ||
insns[s].id == MIPS_INS_LHU ||
insns[s].id == MIPS_INS_LB ||
insns[s].id == MIPS_INS_LBU ||
insns[s].id == MIPS_INS_ADDIU ||
insns[s].id == MIPS_INS_ADD ||
insns[s].id == MIPS_INS_SUB ||
insns[s].id == MIPS_INS_SUBU) {
unsigned int rd = insns[s].operands[0].reg;
if (rt == rd) {
break;
}
} else if (insns[s].id == MIPS_INS_JR &&
insns[s].operands[0].reg == MIPS_REG_RA) {
// stop looking when previous `jr ra` is hit
break;
}
}
break;
}
case MIPS_INS_SD:
case MIPS_INS_SW:
case MIPS_INS_SH:
case MIPS_INS_SB:
case MIPS_INS_LB:
case MIPS_INS_LBU:
case MIPS_INS_LD:
case MIPS_INS_LDL:
case MIPS_INS_LDR:
case MIPS_INS_LH:
case MIPS_INS_LHU:
case MIPS_INS_LW:
case MIPS_INS_LWU:
case MIPS_INS_LDC1:
case MIPS_INS_LWC1:
case MIPS_INS_LWC2:
case MIPS_INS_LWC3:
case MIPS_INS_SWC1:
case MIPS_INS_SWC2:
case MIPS_INS_SWC3:
{
unsigned int mem_rs = insns[i].operands[1].mem.base;
int mem_imm = (int)insns[i].operands[1].mem.disp;
if (mem_rs == MIPS_REG_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.id == MIPS_INS_LW);
//printf("gp 0x%08x %s\n", mem_imm, got_globals[got_entry].name);
unsigned int dest_vaddr = got_globals[got_entry];
insns[i].is_global_got_memop = true;
insns[i].linked_value = dest_vaddr;
//insns[i].label = got_globals[got_entry].name;
//vaddr_references[dest_vaddr].insert(vaddr + i * 4);
//disasm_add_data_addr(state, dest_vaddr);
insns[i].id = MIPS_INS_LI;
insns[i].operands[1].imm = dest_vaddr;
char buf[32];
sprintf(buf, "$%s, 0x%x", cs_reg_name(handle, insn.operands[0].reg), dest_vaddr);
insns[i].op_str = buf;
}
}
} else {
link_with_lui(i, mem_rs, mem_imm);
}
break;
}
case MIPS_INS_ADDIU:
case MIPS_INS_ORI:
{
unsigned int rd = insns[i].operands[0].reg;
unsigned int rs = insns[i].operands[1].reg;
int64_t imm = insns[i].operands[2].imm;
if (rs == MIPS_REG_ZERO) { // becomes LI
char buf[32];
insns[i].id = MIPS_INS_LI;
insns[i].operands[1].imm = imm;
insns[i].mnemonic = "li";
sprintf(buf, "$%s, %" PRIi64, cs_reg_name(handle, rd), imm);
insns[i].op_str = buf;
} else if (/*rd == rs &&*/ rd != MIPS_REG_GP) { // only look for LUI if rd and rs are the same
link_with_lui(i, rs, (int)imm);
}
break;
}
case MIPS_INS_JALR:
{
unsigned int r = insn.operands[0].reg;
if (r == MIPS_REG_T9) {
link_with_jalr(i);
if (insn.linked_insn != -1) {
char buf[32];
sprintf(buf, "0x%x", insn.linked_value);
insn.id = MIPS_INS_JAL;
insn.mnemonic = "jal";
insn.op_str = buf;
insn.operands[0].type = MIPS_OP_IMM;
insn.operands[0].imm = insn.linked_value;
label_addresses.insert(insn.linked_value);
add_function(insn.linked_value);
}
}
break;
}
}
if (insn.id == MIPS_INS_ADDU && insn.operands[0].reg == MIPS_REG_GP && insn.operands[1].reg == MIPS_REG_GP && insn.operands[2].reg == MIPS_REG_T9 && i >= 2) {
//state->function_entry_points.insert(vaddr + (i - 2) * 4);
for (int j = i - 2; j <= i; j++) {
insns[j].id = MIPS_INS_NOP;
insns[j].mnemonic = "nop";
insns[j].op_str = "";
}
}
}
}
static uint32_t addr_to_i(uint32_t addr) {
return (addr - text_vaddr) / 4;
}
static 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.id == MIPS_INS_JR && insn.operands[0].reg == MIPS_REG_RA) {
auto it = find_function(addr);
assert(it != functions.end());
it->second.returns.push_back(addr + 4);
}
if (insn.is_global_got_memop && text_vaddr <= insn.operands[1].imm && insn.operands[1].imm < text_vaddr + text_section_len) {
uint32_t faddr = insn.operands[1].imm;
li_function_pointers.insert(faddr);
functions[faddr].referenced_by_function_pointer = true;
#if INSPECT_FUNCTION_POINTERS
fprintf(stderr, "li 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 <alloc_new+0x14>
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].id = MIPS_INS_JAL;
insns[i].op_count = 1;
insns[i].mnemonic = "jal";
insns[i].op_str = "alloc_new";
insns[i].operands[0].imm = alloc_new_addr;
assert(symbol_names.count(alloc_new_addr) && symbol_names[alloc_new_addr] == "alloc_new");
i++;
if (insns[i + 5].id == MIPS_INS_LI) {
// 7.1
insns[i] = insns[i + 5];
} else {
// 5.3
insns[i] = insns[i + 3];
}
i++;
insns[i].id = MIPS_INS_JR;
insns[i].op_count = 1;
insns[i].mnemonic = "jr";
insns[i].op_str = "$ra";
insns[i].operands[0].reg = MIPS_REG_RA;
it->second.returns.push_back(text_vaddr + i * 4 + 4);
i++;
for (uint32_t j = 0; j < 4; j++) {
insns[i].id = MIPS_INS_NOP;
insns[i].op_count = 0;
insns[i].mnemonic = "nop";
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].id = MIPS_INS_JAL;
insns[i].op_count = 1;
insns[i].mnemonic = "jal";
insns[i].op_str = "alloc_dispose";
insns[i].operands[0].imm = alloc_dispose_addr;
assert(symbol_names.count(alloc_dispose_addr) && symbol_names[alloc_dispose_addr] == "alloc_dispose");
i++;
insns[i] = insns[i + 2];
i++;
insns[i].id = MIPS_INS_JR;
insns[i].op_count = 1;
insns[i].mnemonic = "jr";
insns[i].op_str = "$ra";
insns[i].operands[0].reg = MIPS_REG_RA;
it->second.returns.push_back(text_vaddr + i * 4 + 4);
i++;
insns[i].id = MIPS_INS_NOP;
insns[i].op_count = 0;
insns[i].mnemonic = "nop";
} else if (insns[i].id == MIPS_INS_LW && insns[i + 1].id == MIPS_INS_MOVE && insns[i + 2].id == MIPS_INS_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;
}
}
}
static 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);
}
static 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.id) {
case MIPS_INS_BEQ:
case MIPS_INS_BGEZ:
case MIPS_INS_BGTZ:
case MIPS_INS_BLEZ:
case MIPS_INS_BLTZ:
case MIPS_INS_BNE:
case MIPS_INS_BEQZ:
case MIPS_INS_BNEZ:
case MIPS_INS_BC1F:
case MIPS_INS_BC1T:
add_edge(i, i + 1);
add_edge(i + 1, addr_to_i((uint32_t)insn.operands[insn.op_count - 1].imm));
break;
case MIPS_INS_BEQL:
case MIPS_INS_BGEZL:
case MIPS_INS_BGTZL:
case MIPS_INS_BLEZL:
case MIPS_INS_BLTZL:
case MIPS_INS_BNEL:
case MIPS_INS_BC1FL:
case MIPS_INS_BC1TL:
add_edge(i, i + 1);
add_edge(i, i + 2);
add_edge(i + 1, addr_to_i((uint32_t)insn.operands[insn.op_count - 1].imm));
insns[i + 1].no_following_successor = true; // don't inspect delay slot
break;
case MIPS_INS_B:
case MIPS_INS_J:
add_edge(i, i + 1);
add_edge(i + 1, addr_to_i((uint32_t)insn.operands[0].imm));
insns[i + 1].no_following_successor = true; // don't inspect delay slot
break;
case MIPS_INS_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 * 4 <= 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 * 4) + gp_value;
add_edge(i + 1, addr_to_i(dest_addr));
}
} else {
assert(insn.operands[0].reg == MIPS_REG_RA && "jump to address in register not supported");
}
insns[i + 1].no_following_successor = true; // don't inspect delay slot
break;
}
case MIPS_INS_JAL: {
add_edge(i, i + 1);
uint32_t dest = (uint32_t)insn.operands[0].imm;
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 MIPS_INS_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;
}
}
}
static uint64_t map_reg(int32_t reg) {
if (reg > MIPS_REG_31) {
if (reg == MIPS_REG_HI) {
reg = MIPS_REG_31 + 1;
} else if (reg == MIPS_REG_LO) {
reg = MIPS_REG_31 + 2;
} else {
return 0;
}
}
return (uint64_t)1 << (reg - MIPS_REG_0 + 1);
}
static uint64_t temporary_regs(void) {
return
map_reg(MIPS_REG_T0) |
map_reg(MIPS_REG_T1) |
map_reg(MIPS_REG_T2) |
map_reg(MIPS_REG_T3) |
map_reg(MIPS_REG_T4) |
map_reg(MIPS_REG_T5) |
map_reg(MIPS_REG_T6) |
map_reg(MIPS_REG_T7) |
map_reg(MIPS_REG_T8) |
map_reg(MIPS_REG_T9);
}
typedef enum {
TYPE_NOP,
TYPE_1S,
TYPE_2S,
TYPE_1D,
TYPE_1D_1S,
TYPE_1D_2S,
TYPE_D_LO_HI_2S,
TYPE_1S_POS1
} TYPE;
static TYPE insn_to_type(Insn& i) {
switch (i.id) {
case MIPS_INS_ADD:
case MIPS_INS_ADDU:
if (i.mnemonic != "add.s" && i.mnemonic != "add.d") {
return TYPE_1D_2S;
} else {
return TYPE_NOP;
}
case MIPS_INS_ADDI:
case MIPS_INS_ADDIU:
case MIPS_INS_ANDI:
case MIPS_INS_ORI:
case MIPS_INS_LB:
case MIPS_INS_LBU:
case MIPS_INS_LH:
case MIPS_INS_LHU:
case MIPS_INS_LW:
case MIPS_INS_LWL:
//case MIPS_INS_LWR:
case MIPS_INS_MOVE:
case MIPS_INS_NEGU:
case MIPS_INS_NOT:
case MIPS_INS_SLL:
case MIPS_INS_SLTI:
case MIPS_INS_SLTIU:
case MIPS_INS_SRA:
case MIPS_INS_SRL:
case MIPS_INS_XORI:
return TYPE_1D_1S;
case MIPS_INS_MFHI:
i.operands[1].reg = MIPS_REG_HI;
return TYPE_1D_1S;
case MIPS_INS_MFLO:
i.operands[1].reg = MIPS_REG_LO;
return TYPE_1D_1S;
case MIPS_INS_AND:
case MIPS_INS_OR:
case MIPS_INS_NOR:
case MIPS_INS_SLLV:
case MIPS_INS_SLT:
case MIPS_INS_SLTU:
case MIPS_INS_SRAV:
case MIPS_INS_SRLV:
case MIPS_INS_SUBU:
case MIPS_INS_XOR:
return TYPE_1D_2S;
case MIPS_INS_CFC1:
case MIPS_INS_MFC1:
case MIPS_INS_LI:
case MIPS_INS_LUI:
return TYPE_1D;
case MIPS_INS_CTC1:
case MIPS_INS_BGEZ:
case MIPS_INS_BGEZL:
case MIPS_INS_BGTZ:
case MIPS_INS_BGTZL:
case MIPS_INS_BLEZ:
case MIPS_INS_BLEZL:
case MIPS_INS_BLTZ:
case MIPS_INS_BLTZL:
case MIPS_INS_BEQZ:
case MIPS_INS_BNEZ:
case MIPS_INS_MTC1:
return TYPE_1S;
case MIPS_INS_BEQ:
case MIPS_INS_BEQL:
case MIPS_INS_BNE:
case MIPS_INS_BNEL:
case MIPS_INS_SB:
case MIPS_INS_SH:
case MIPS_INS_SW:
case MIPS_INS_SWL:
//case MIPS_INS_SWR:
case MIPS_INS_TNE:
case MIPS_INS_TEQ:
case MIPS_INS_TGE:
case MIPS_INS_TGEU:
case MIPS_INS_TLT:
return TYPE_2S;
case MIPS_INS_DIV:
if (i.mnemonic != "div.s" && i.mnemonic != "div.d") {
return TYPE_D_LO_HI_2S;
} else {
return TYPE_NOP;
}
case MIPS_INS_DIVU:
case MIPS_INS_MULT:
case MIPS_INS_MULTU:
return TYPE_D_LO_HI_2S;
case MIPS_INS_NEG:
if (i.mnemonic != "neg.s" && i.mnemonic != "neg.d") {
return TYPE_1D_1S;
} else {
return TYPE_NOP;
}
case MIPS_INS_JALR:
return TYPE_1S;
case MIPS_INS_JR:
if (i.jtbl_addr != 0) {
i.operands[0].reg = i.index_reg;
}
if (i.operands[0].reg == MIPS_REG_RA) {
return TYPE_NOP;
}
return TYPE_1S;
case MIPS_INS_LWC1:
case MIPS_INS_LDC1:
case MIPS_INS_SWC1:
case MIPS_INS_SDC1:
return TYPE_1S_POS1;
default:
return TYPE_NOP;
}
}
static void pass4(void) {
vector<uint32_t> q;
uint64_t livein_func_start = 1U | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) | map_reg(MIPS_REG_SP) | map_reg(MIPS_REG_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(MIPS_REG_A2) | map_reg(MIPS_REG_A3);
}
for (auto& addr : li_function_pointers) {
q.push_back(addr);
insns[addr_to_i(addr)].f_livein = livein_func_start | map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3);
}
while (!q.empty()) {
uint32_t addr = q.back();
q.pop_back();
uint32_t idx = addr_to_i(addr);
Insn& i = insns[idx];
uint64_t live = i.f_livein | 1;
switch (insn_to_type(i)) {
case TYPE_1D:
live |= map_reg(i.operands[0].reg);
break;
case TYPE_1D_1S:
if (live & map_reg(i.operands[1].reg)) {
live |= map_reg(i.operands[0].reg);
}
break;
case TYPE_1D_2S:
if ((live & map_reg(i.operands[1].reg)) && (live & map_reg(i.operands[2].reg))) {
live |= map_reg(i.operands[0].reg);
}
break;
case TYPE_D_LO_HI_2S:
if ((live & map_reg(i.operands[0].reg)) && (live & map_reg(i.operands[1].reg))) {
live |= map_reg(MIPS_REG_LO);
live |= map_reg(MIPS_REG_HI);
}
break;
}
if ((i.f_liveout | live) == i.f_liveout) {
// No new bits
continue;
}
live |= i.f_liveout;
i.f_liveout = live;
bool function_entry = false;
for (Edge& e : i.successors) {
uint64_t new_live = live;
if (e.function_exit) {
new_live &= 1U | map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_V1) | map_reg(MIPS_REG_ZERO);
} else if (e.function_entry) {
new_live &= 1U | map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_SP) | map_reg(MIPS_REG_ZERO);
function_entry = true;
} else if (e.extern_function) {
string name;
bool is_extern_function = false;
size_t extern_function_id;
auto it = symbol_names.find(insns[idx - 1].operands[0].imm);
if (it != symbol_names.end()) {
name = it->second;
for (size_t i = 0; i < sizeof(extern_functions) / sizeof(extern_functions[0]); i++) {
if (name == extern_functions[i].name) {
is_extern_function = true;
extern_function_id = i;
break;
}
}
if (!is_extern_function) {
fprintf(stderr, "missing extern function: %s\n", name.c_str());
}
}
assert(is_extern_function);
auto& fn = extern_functions[extern_function_id];
char ret_type = fn.params[0];
new_live &= ~(map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_V1) | temporary_regs());
switch (ret_type) {
case 'i':
case 'u':
case 'p':
new_live |= map_reg(MIPS_REG_V0);
break;
case 'f':
break;
case 'd':
break;
case 'v':
break;
case 'l':
case 'j':
new_live |= map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_V1);
break;
}
} else if (e.function_pointer) {
new_live &= ~(map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_V1) | temporary_regs());
new_live |= map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_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 * 4);
}
}
if (function_entry) {
// add one edge that skips the function call, for callee-saved register liveness propagation
live &= ~(map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_V1) | temporary_regs());
if ((insns[idx + 1].f_livein | live) != insns[idx + 1].f_livein) {
insns[idx + 1].f_livein |= live;
q.push_back(text_vaddr + (idx + 1) * 4);
}
}
}
}
static void pass5(void) {
vector<uint32_t> 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(MIPS_REG_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(MIPS_REG_V0) | map_reg(MIPS_REG_V1);
}
}
for (auto& func_addr : li_function_pointers) {
for (auto addr : functions[func_addr].returns) {
q.push_back(addr);
insns[addr_to_i(addr)].b_liveout = 1U | map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_V1);
}
}
for (size_t i = 0; i < insns.size(); i++) {
if (insns[i].f_livein != 0) {
// Instruction is reachable
q.push_back(text_vaddr + i * 4);
}
}
while (!q.empty()) {
uint32_t addr = q.back();
q.pop_back();
uint32_t idx = addr_to_i(addr);
Insn& i = insns[idx];
uint64_t live = i.b_liveout | 1;
switch (insn_to_type(i)) {
case TYPE_1S:
live |= map_reg(i.operands[0].reg);
break;
case TYPE_1S_POS1:
live |= map_reg(i.operands[1].reg);
break;
case TYPE_2S:
live |= map_reg(i.operands[0].reg);
live |= map_reg(i.operands[1].reg);
break;
case TYPE_1D:
live &= ~map_reg(i.operands[0].reg);
break;
case TYPE_1D_1S:
if (live & map_reg(i.operands[0].reg)) {
live &= ~map_reg(i.operands[0].reg);
live |= map_reg(i.operands[1].reg);
}
break;
case TYPE_1D_2S:
if (live & map_reg(i.operands[0].reg)) {
live &= ~map_reg(i.operands[0].reg);
live |= map_reg(i.operands[1].reg);
live |= map_reg(i.operands[2].reg);
}
break;
case TYPE_D_LO_HI_2S: {
bool used = (live & map_reg(MIPS_REG_LO)) || (live & map_reg(MIPS_REG_HI));
live &= ~map_reg(MIPS_REG_LO);
live &= ~map_reg(MIPS_REG_HI);
if (used) {
live |= map_reg(i.operands[0].reg);
live |= map_reg(i.operands[1].reg);
}
break;
}
}
if ((i.b_livein | live) == i.b_livein) {
// No new bits
continue;
}
live |= i.b_livein;
i.b_livein = live;
bool function_exit = false;
for (Edge& e : i.predecessors) {
uint64_t new_live = live;
if (e.function_exit) {
new_live &= 1U | map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_V1);
function_exit = true;
} else if (e.function_entry) {
new_live &= 1U | map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_SP);
} else if (e.extern_function) {
string name;
bool is_extern_function = false;
size_t extern_function_id;
auto it = symbol_names.find(insns[idx - 2].operands[0].imm);
if (it != symbol_names.end()) {
name = it->second;
for (size_t i = 0; i < sizeof(extern_functions) / sizeof(extern_functions[0]); i++) {
if (name == extern_functions[i].name) {
is_extern_function = true;
extern_function_id = i;
break;
}
}
}
assert(is_extern_function);
auto& fn = extern_functions[extern_function_id];
uint64_t args = 1U;
if (fn.flags & FLAG_VARARG) {
// Assume the worst, that all four registers are used
for (int j = 0; j < 4; j++) {
args |= map_reg(MIPS_REG_A0 + j);
}
}
int pos = 0;
int pos_float = 0;
bool only_floats_so_far = true;
for (const char *p = 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(MIPS_REG_A0 + pos);
}
++pos;
break;
case 'f':
if (only_floats_so_far && pos_float < 4) {
pos_float += 2;
} else if (pos < 4) {
args |= map_reg(MIPS_REG_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(MIPS_REG_A0 + pos) | map_reg(MIPS_REG_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(MIPS_REG_A0 + pos) | map_reg(MIPS_REG_A0 + pos + 1);
}
pos += 2;
break;
}
}
args |= map_reg(MIPS_REG_SP);
new_live &= ~(map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_V1) | temporary_regs());
new_live |= args;
} else if (e.function_pointer) {
new_live &= ~(map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_V1) | temporary_regs());
new_live |= map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) | map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_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 * 4);
}
}
if (function_exit) {
// add one edge that skips the function call, for callee-saved register liveness propagation
live &= ~(map_reg(MIPS_REG_V0) | map_reg(MIPS_REG_A0) | map_reg(MIPS_REG_A1) |
map_reg(MIPS_REG_A2) | map_reg(MIPS_REG_A3) | map_reg(MIPS_REG_V1) | temporary_regs());
if ((insns[idx - 1].b_liveout | live) != insns[idx - 1].b_liveout) {
insns[idx - 1].b_liveout |= live;
q.push_back(text_vaddr + (idx - 1) * 4);
}
}
}
}
static 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(MIPS_REG_V1)) {
f.nret = 2;
} else if ((i.f_liveout & i.b_liveout & map_reg(MIPS_REG_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(MIPS_REG_A0 + i)) {
f.nargs = 1 + i;
}
}
f.v0_in = (insn.f_livein & insn.b_livein & map_reg(MIPS_REG_V0)) != 0 && !f.referenced_by_function_pointer;
}
}
static void dump(void) {
for (size_t i = 0; i < insns.size(); i++) {
Insn& insn = insns[i];
uint32_t vaddr = text_vaddr + i * 4;
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);
}
}
printf("\t%s %s\n", insn.mnemonic.c_str(), insn.op_str.c_str());
}
}
static const char *r(uint32_t reg) {
return cs_reg_name(handle, reg);
}
static const char *wr(uint32_t reg) {
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]"
};
assert(reg >= MIPS_REG_F0 && reg <= MIPS_REG_F31);
return regs[reg - MIPS_REG_F0];
}
static const char *fr(uint32_t reg) {
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]"
};
assert(reg >= MIPS_REG_F0 && reg <= MIPS_REG_F31);
return regs[reg - MIPS_REG_F0];
}
static const char *dr(uint32_t reg) {
static const char *regs[] = {
"f0.d",
"f2.d",
"f4.d",
"f6.d",
"f8.d",
"f10.d",
"f12.d",
"f14.d",
"f16.d",
"f18.d",
"f20.d",
"f22.d",
"f24.d",
"f26.d",
"f28.d",
"f30.d"
};
assert(reg >= MIPS_REG_F0 && reg <= MIPS_REG_F31 && (reg - MIPS_REG_F0) % 2 == 0);
return regs[(reg - MIPS_REG_F0) / 2];
}
static void dump_instr(int i);
static 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) {", cast1, lhs, op, cast2, rhs);
dump_instr(i + 1);
printf("goto L%x;}\n", (uint32_t)insn.operands[insn.op_count - 1].imm);
}
static void dump_cond_branch_likely(int i, const char *lhs, const char *op, const char *rhs) {
uint32_t target = text_vaddr + (i + 2) * 4;
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);
}
static void dump_instr(int i) {
const char *symbol_name = NULL;
if (symbol_names.count(text_vaddr + i * 4) != 0) {
symbol_name = symbol_names[text_vaddr + i * 4].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 : "");
}
Insn& insn = insns[i];
if (!insn.is_jump && !conservative) {
switch (insn_to_type(insn)) {
case TYPE_1S:
if (!(insn.f_livein & map_reg(insn.operands[0].reg))) {
printf("// fdead %llx ", (unsigned long long)insn.f_livein);
}
break;
case TYPE_1S_POS1:
if (!(insn.f_livein & map_reg(insn.operands[1].reg))) {
printf("// fdead %llx ", (unsigned long long)insn.f_livein);
}
break;
case TYPE_2S:
if (!(insn.f_livein & map_reg(insn.operands[0].reg)) || !(insn.f_livein & map_reg(insn.operands[1].reg))) {
printf("// fdead %llx ", (unsigned long long)insn.f_livein);
}
break;
case TYPE_1D_2S:
if (!(insn.f_livein & map_reg(insn.operands[2].reg))) {
printf("// fdead %llx ", (unsigned long long)insn.f_livein);
break;
}
// fallthrough
case TYPE_1D_1S:
if (!(insn.f_livein & map_reg(insn.operands[1].reg))) {
printf("// fdead %llx ", (unsigned long long)insn.f_livein);
break;
}
// fallthrough
case TYPE_1D:
if (!(insn.b_liveout & map_reg(insn.operands[0].reg))) {
printf("// bdead %llx ", (unsigned long long)insn.b_liveout);
}
break;
case TYPE_D_LO_HI_2S:
if (!(insn.f_livein & map_reg(insn.operands[0].reg)) || !(insn.f_livein & map_reg(insn.operands[1].reg))) {
printf("// fdead %llx ", (unsigned long long)insn.f_livein);
break;
}
if (!(insn.b_liveout & (map_reg(MIPS_REG_LO) | map_reg(MIPS_REG_HI)))) {
printf("// bdead %llx ", (unsigned long long)insn.b_liveout);
}
break;
}
}
switch (insn.id) {
case MIPS_INS_ADD:
case MIPS_INS_ADDU:
if (insn.mnemonic == "add.s") {
printf("%s = %s + %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg), fr(insn.operands[2].reg));
} else if (insn.mnemonic == "add.d") {
printf("%s = %s + %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg), dr(insn.operands[2].reg));
} else {
printf("%s = %s + %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
}
break;
case MIPS_INS_ADDI:
case MIPS_INS_ADDIU:
printf("%s = %s + 0x%x;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_AND:
printf("%s = %s & %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_ANDI:
printf("%s = %s & 0x%x;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_BEQ:
dump_cond_branch(i, r(insn.operands[0].reg), "==", r(insn.operands[1].reg));
break;
case MIPS_INS_BEQL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), "==", r(insn.operands[1].reg));
break;
case MIPS_INS_BGEZ:
dump_cond_branch(i, r(insn.operands[0].reg), ">=", "0");
break;
case MIPS_INS_BGEZL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), ">=", "0");
break;
case MIPS_INS_BGTZ:
dump_cond_branch(i, r(insn.operands[0].reg), ">", "0");
break;
case MIPS_INS_BGTZL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), ">", "0");
break;
case MIPS_INS_BLEZ:
dump_cond_branch(i, r(insn.operands[0].reg), "<=", "0");
break;
case MIPS_INS_BLEZL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), "<=", "0");
break;
case MIPS_INS_BLTZ:
dump_cond_branch(i, r(insn.operands[0].reg), "<", "0");
break;
case MIPS_INS_BLTZL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), "<", "0");
break;
case MIPS_INS_BNE:
dump_cond_branch(i, r(insn.operands[0].reg), "!=", r(insn.operands[1].reg));
break;
case MIPS_INS_BNEL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), "!=", insn.mnemonic == "bnezl" ? "0" : r(insn.operands[1].reg));
break;
case MIPS_INS_BREAK:
printf("abort();\n");
break;
case MIPS_INS_BEQZ:
dump_cond_branch(i, r(insn.operands[0].reg), "==", "0");
break;
/*case MIPS_INS_BEQZL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), "==", "0");
break;*/
case MIPS_INS_B:
dump_instr(i + 1);
printf("goto L%x;\n", (int32_t)insn.operands[0].imm);
break;
case MIPS_INS_BC1F:
case MIPS_INS_BC1T:
printf("if (%scf) {", insn.id == MIPS_INS_BC1F ? "!" : "");
dump_instr(i + 1);
printf("goto L%x;}\n", (int32_t)insn.operands[0].imm);
break;
case MIPS_INS_BC1FL:
case MIPS_INS_BC1TL:
{
uint32_t target = text_vaddr + (i + 2) * 4;
printf("if (%scf) {", insn.id == MIPS_INS_BC1FL ? "!" : "");
dump_instr(i + 1);
printf("goto L%x;}\n", (int32_t)insn.operands[0].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 MIPS_INS_BNEZ:
dump_cond_branch(i, r(insn.operands[0].reg), "!=", "0");
break;
/*case MIPS_INS_BNEZL:
dump_cond_branch_likely(i, r(insn.operands[0].reg), "!=", "0");
break;*/
case MIPS_INS_C:
if (insn.mnemonic == "c.lt.s") {
printf("cf = %s < %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "c.le.s") {
printf("cf = %s <= %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "c.eq.s") {
printf("cf = %s == %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "c.lt.d") {
printf("cf = %s < %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else if (insn.mnemonic == "c.le.d") {
printf("cf = %s <= %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else if (insn.mnemonic == "c.eq.d") {
printf("cf = %s == %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg));
}
break;
case MIPS_INS_CVT:
if (insn.mnemonic == "cvt.s.w") {
printf("%s = (int)%s;\n", fr(insn.operands[0].reg), wr(insn.operands[1].reg));
} else if (insn.mnemonic == "cvt.d.w") {
printf("%s = (int)%s;\n", dr(insn.operands[0].reg), wr(insn.operands[1].reg));
} else if (insn.mnemonic == "cvt.d.s") {
printf("%s = %s;\n", dr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "cvt.s.d") {
printf("%s = %s;\n", fr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else if (insn.mnemonic == "cvt.w.d") {
printf("%s = cvt_w_d(%s);\n", wr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else if (insn.mnemonic == "cvt.w.s") {
printf("%s = cvt_w_s(%s);\n", wr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else {
goto unimplemented;
}
break;
case MIPS_INS_CFC1:
assert(insn.operands[1].reg == MIPS_REG_31);
printf("%s = fcsr;\n", r(insn.operands[0].reg));
break;
case MIPS_INS_CTC1:
assert(insn.operands[1].reg == MIPS_REG_31);
printf("fcsr = %s;\n", r(insn.operands[0].reg));
break;
case MIPS_INS_DIV:
if (insn.mnemonic == "div.s") {
assert(insn.op_count == 3);
printf("%s = %s / %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg), fr(insn.operands[2].reg));
} else if (insn.mnemonic == "div.d") {
assert(insn.op_count == 3);
printf("%s = %s / %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg), dr(insn.operands[2].reg));
} else {
assert(insn.op_count == 2);
printf("lo = (int)%s / (int)%s; ", r(insn.operands[0].reg), r(insn.operands[1].reg));
printf("hi = (int)%s %% (int)%s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
}
break;
case MIPS_INS_DIVU:
assert(insn.op_count == 2);
printf("lo = %s / %s; ", r(insn.operands[0].reg), r(insn.operands[1].reg));
printf("hi = %s %% %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
break;
case MIPS_INS_MOV:
if (insn.mnemonic == "mov.s") {
printf("%s = %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "mov.d") {
printf("%s = %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else {
goto unimplemented;
}
break;
case MIPS_INS_MUL:
if (insn.mnemonic == "mul.s") {
printf("%s = %s * %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg), fr(insn.operands[2].reg));
} else if (insn.mnemonic == "mul.d") {
printf("%s = %s * %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg), dr(insn.operands[2].reg));
} else {
goto unimplemented;
}
break;
case MIPS_INS_NEG:
if (insn.mnemonic == "neg.s") {
printf("%s = -%s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "neg.d") {
printf("%s = -%s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else {
printf("%s = -%s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
}
break;
case MIPS_INS_SUB:
if (insn.mnemonic == "sub.s") {
printf("%s = %s - %s;\n", fr(insn.operands[0].reg), fr(insn.operands[1].reg), fr(insn.operands[2].reg));
} else if (insn.mnemonic == "sub.d") {
printf("%s = %s - %s;\n", dr(insn.operands[0].reg), dr(insn.operands[1].reg), dr(insn.operands[2].reg));
} else {
goto unimplemented;
}
break;
case MIPS_INS_J:
dump_instr(i + 1);
printf("goto L%x;\n", (uint32_t)insn.operands[0].imm);
break;
case MIPS_INS_JAL:
{
string name;
bool is_extern_function = false;
size_t extern_function_id;
auto it = symbol_names.find(insn.operands[0].imm);
if (it != symbol_names.end()) {
name = it->second;
for (size_t i = 0; i < sizeof(extern_functions) / sizeof(extern_functions[0]); i++) {
if (name == extern_functions[i].name) {
is_extern_function = true;
extern_function_id = i;
break;
}
}
}
dump_instr(i + 1);
if (is_extern_function) {
auto& fn = extern_functions[extern_function_id];
if (fn.flags & FLAG_VARARG) {
for (int j = 0; j < 4; j++) {
printf("MEM_U32(sp + %d) = %s;\n", j * 4, r(MIPS_REG_A0 + j));
}
}
char ret_type = fn.params[0];
if (ret_type != 'v') {
switch (ret_type) {
case 'i':
case 'u':
case 'p':
printf("%s = ", r(MIPS_REG_V0));
break;
case 'f':
printf("%s = ", fr(MIPS_REG_F0));
break;
case 'd':
printf("%s = ", dr(MIPS_REG_F0));
break;
case 'l':
case 'j':
printf("temp64 = ");
break;
}
}
printf("wrapper_%s(", name.c_str());
bool first = true;
if (!(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 = 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(MIPS_REG_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(MIPS_REG_F12 + pos_float));
pos_float += 2;
} else if (pos < 4) {
printf("BITCAST_U32_TO_F32(%s)", r(MIPS_REG_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("%s", dr(MIPS_REG_F12 + pos_float));
pos_float += 2;
} else if (pos < 4) {
printf("BITCAST_U64_TO_F64(((uint64_t)%s << 32) | (uint64_t)%s)", r(MIPS_REG_A0 + pos), r(MIPS_REG_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(MIPS_REG_A0 + pos), r(MIPS_REG_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 ((fn.flags & FLAG_VARARG) || needs_sp) {
printf("%s%s", first ? "" : ", ", r(MIPS_REG_SP));
}
printf(");\n");
if (ret_type == 'l' || ret_type == 'j') {
printf("%s = (uint32_t)(temp64 >> 32);\n", r(MIPS_REG_V0));
printf("%s = (uint32_t)temp64;\n", r(MIPS_REG_V1));
}
if (!name.empty()) {
//printf("printf(\"%s %%x\\n\", %s);\n", name.c_str(), r(MIPS_REG_A0));
}
} else {
Function& f = functions.find((uint32_t)insn.operands[0].imm)->second;
if (f.nret == 1) {
printf("v0 = ");
} else if (f.nret == 2) {
printf("temp64 = ");
}
if (!name.empty()) {
//printf("printf(\"%s %%x\\n\", %s);\n", name.c_str(), r(MIPS_REG_A0));
printf("f_%s", name.c_str());
} else {
printf("func_%x", (uint32_t)insn.operands[0].imm);
}
printf("(mem, sp");
if (f.v0_in) {
printf(", %s", r(MIPS_REG_V0));
}
for (uint32_t i = 0; i < f.nargs; i++) {
printf(", %s", r(MIPS_REG_A0 + i));
}
printf(");\n");
if (f.nret == 2) {
printf("%s = (uint32_t)(temp64 >> 32);\n", r(MIPS_REG_V0));
printf("%s = (uint32_t)temp64;\n", r(MIPS_REG_V1));
}
}
printf("goto L%x;\n", text_vaddr + (i + 2) * 4);
label_addresses.insert(text_vaddr + (i + 2) * 4);
break;
}
case MIPS_INS_JALR:
printf("fp_dest = %s;\n", r(insn.operands[0].reg));
dump_instr(i + 1);
printf("temp64 = trampoline(mem, sp, %s, %s, %s, %s, fp_dest);\n",
r(MIPS_REG_A0), r(MIPS_REG_A1), r(MIPS_REG_A2), r(MIPS_REG_A3));
printf("%s = (uint32_t)(temp64 >> 32);\n", r(MIPS_REG_V0));
printf("%s = (uint32_t)temp64;\n", r(MIPS_REG_V1));
printf("goto L%x;\n", text_vaddr + (i + 2) * 4);
label_addresses.insert(text_vaddr + (i + 2) * 4);
break;
case MIPS_INS_JR:
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 * 4 <= 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 * 4) + 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(insn.index_reg));
dump_instr(i + 1);
printf("goto *dest;\n");
#else
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 * 4) + gp_value;
printf("case %u: goto L%x;\n", i, dest_addr);
label_addresses.insert(dest_addr);
}
printf("}\n");
#endif
} else {
if (insn.operands[0].reg != MIPS_REG_RA) {
printf("UNSUPPORTED JR %s %s\n", insn.op_str.c_str(), r(insn.operands[0].reg));
} else {
dump_instr(i + 1);
switch (find_function(text_vaddr + i * 4)->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 MIPS_INS_LB:
printf("%s = MEM_S8(%s + %d);\n", r(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LBU:
printf("%s = MEM_U8(%s + %d);\n", r(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LH:
printf("%s = MEM_S16(%s + %d);\n", r(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LHU:
printf("%s = MEM_U16(%s + %d);\n", r(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LUI:
printf("%s = 0x%x;\n", r(insn.operands[0].reg), ((uint32_t)insn.operands[1].imm) << 16);
break;
case MIPS_INS_LW:
printf("%s = MEM_U32(%s + %d);\n", r(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LWC1:
printf("%s = MEM_U32(%s + %d);\n", wr(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LDC1:
assert((insn.operands[0].reg - MIPS_REG_F0) % 2 == 0);
printf("%s = MEM_U32(%s + %d);\n", wr(insn.operands[0].reg + 1), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
printf("%s = MEM_U32(%s + %d + 4);\n", wr(insn.operands[0].reg), r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
break;
case MIPS_INS_LWL:
{
const char *reg = r(insn.operands[0].reg);
printf("%s = %s + %d; ", reg, r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp);
printf("%s = (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 MIPS_INS_LWR:
printf("//lwr %s\n", insn.op_str.c_str());
break;
case MIPS_INS_LI:
if (insn.is_global_got_memop && text_vaddr <= insn.operands[1].imm && insn.operands[1].imm < text_vaddr + text_section_len) {
printf("%s = 0x%x; // function pointer\n", r(insn.operands[0].reg), (uint32_t)insn.operands[1].imm);
label_addresses.insert((uint32_t)insn.operands[1].imm);
} else {
printf("%s = 0x%x;\n", r(insn.operands[0].reg), (uint32_t)insn.operands[1].imm);
}
break;
case MIPS_INS_MFC1:
printf("%s = %s;\n", r(insn.operands[0].reg), wr(insn.operands[1].reg));
break;
case MIPS_INS_MFHI:
printf("%s = hi;\n", r(insn.operands[0].reg));
break;
case MIPS_INS_MFLO:
printf("%s = lo;\n", r(insn.operands[0].reg));
break;
case MIPS_INS_MOVE:
printf("%s = %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
break;
case MIPS_INS_MTC1:
printf("%s = %s;\n", wr(insn.operands[1].reg), r(insn.operands[0].reg));
break;
case MIPS_INS_MULT:
printf("lo = %s * %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
printf("hi = (uint32_t)((int64_t)(int)%s * (int64_t)(int)%s >> 32);\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
break;
case MIPS_INS_MULTU:
printf("lo = %s * %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
printf("hi = (uint32_t)((uint64_t)%s * (uint64_t)%s >> 32);\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
break;
case MIPS_INS_NEGU:
printf("%s = -%s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
break;
case MIPS_INS_NOR:
printf("%s = ~(%s | %s);\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_NOT:
printf("%s = ~%s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg));
break;
case MIPS_INS_OR:
printf("%s = %s | %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_ORI:
printf("%s = %s | 0x%x;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_SB:
printf("MEM_U8(%s + %d) = (uint8_t)%s;\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, r(insn.operands[0].reg));
break;
case MIPS_INS_SH:
printf("MEM_U16(%s + %d) = (uint16_t)%s;\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, r(insn.operands[0].reg));
break;
case MIPS_INS_SLL:
printf("%s = %s << %d;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_SLLV:
printf("%s = %s << (%s & 0x1f);\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_SLT:
printf("%s = (int)%s < (int)%s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_SLTI:
printf("%s = (int)%s < (int)0x%x;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_SLTIU:
printf("%s = %s < 0x%x;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_SLTU:
printf("%s = %s < %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_SRA:
printf("%s = (int)%s >> %d;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_SRAV:
printf("%s = (int)%s >> (%s & 0x1f);\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_SRL:
printf("%s = %s >> %d;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_SRLV:
printf("%s = %s >> (%s & 0x1f);\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_SUBU:
printf("%s = %s - %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_SW:
printf("MEM_U32(%s + %d) = %s;\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, r(insn.operands[0].reg));
break;
case MIPS_INS_SWC1:
printf("MEM_U32(%s + %d) = %s;\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, wr(insn.operands[0].reg));
break;
case MIPS_INS_SDC1:
assert((insn.operands[0].reg - MIPS_REG_F0) % 2 == 0);
printf("MEM_U32(%s + %d) = %s;\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, wr(insn.operands[0].reg + 1));
printf("MEM_U32(%s + %d + 4) = %s;\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, wr(insn.operands[0].reg));
break;
case MIPS_INS_SWL:
for (int i = 0; i < 4; i++) {
printf("MEM_U8(%s + %d + %d) = (uint8_t)(%s >> %d);\n", r(insn.operands[1].mem.base), (int)insn.operands[1].mem.disp, i, r(insn.operands[0].reg), (3 - i) * 8);
}
break;
case MIPS_INS_SWR:
printf("//swr %s\n", insn.op_str.c_str());
break;
case MIPS_INS_TRUNC:
if (insn.mnemonic == "trunc.w.s") {
printf("%s = (int)%s;\n", wr(insn.operands[0].reg), fr(insn.operands[1].reg));
} else if (insn.mnemonic == "trunc.w.d") {
printf("%s = (int)%s;\n", wr(insn.operands[0].reg), dr(insn.operands[1].reg));
} else {
goto unimplemented;
}
break;
case MIPS_INS_XOR:
printf("%s = %s ^ %s;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), r(insn.operands[2].reg));
break;
case MIPS_INS_XORI:
printf("%s = %s ^ 0x%x;\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (uint32_t)insn.operands[2].imm);
break;
case MIPS_INS_TNE:
printf("assert(%s == %s && \"tne %d\");\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (int)insn.operands[2].imm);
break;
case MIPS_INS_TEQ:
printf("assert(%s != %s && \"teq %d\");\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (int)insn.operands[2].imm);
break;
case MIPS_INS_TGE:
printf("assert((int)%s < (int)%s && \"tge %d\");\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (int)insn.operands[2].imm);
break;
case MIPS_INS_TGEU:
printf("assert(%s < %s && \"tgeu %d\");\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (int)insn.operands[2].imm);
break;
case MIPS_INS_TLT:
printf("assert((int)%s >= (int)%s && \"tlt %d\");\n", r(insn.operands[0].reg), r(insn.operands[1].reg), (int)insn.operands[2].imm);
break;
case MIPS_INS_NOP:
printf("//nop;\n");
break;
default:
unimplemented:
printf("UNIMPLEMENTED %s %s\n", insn.mnemonic.c_str(), insn.op_str.c_str());
break;
}
}
static void inspect_data_function_pointers(vector<pair<uint32_t, uint32_t>>& 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;
}
}
}
static 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(MIPS_REG_V0));
}
for (uint32_t i = 0; i < f.nargs; i++) {
printf(", uint32_t %s", r(MIPS_REG_A0 + i));
}
printf(")");
}
static void dump_c(void) {
map<string, uint32_t> symbol_names_inv;
for (auto& it : symbol_names) {
symbol_names_inv[it.second] = it.first;
}
uint32_t min_addr = ~0;
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);
}
min_addr = min_addr & ~0xfff;
max_addr = (max_addr + 0xfff) & ~0xfff;
uint32_t stack_bottom = min_addr;
min_addr -= 1 * 1024 * 1024; // 1 MB stack
stack_bottom -= 16; // 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) {
if (insns[addr_to_i(f_it.first)].f_livein != 0) {
// Function is used
dump_function_signature(f_it.second, f_it.first);
printf(";\n");
}
}
if (!data_function_pointers.empty() || !li_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 (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("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(MIPS_REG_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);
}
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);
}*/
}
static 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, *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<string> 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;
//disasm_got_entries_set(state, gp_base, gp_adj, local_entries, local_got_no, global_entries, global_got_no);
//out_range.common_start = common_start;
//out_range.common_order.swap(common_order);
}
// 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;
}
int main(int argc, char *argv[]) {
const char *filename = argv[1];
if (strcmp(filename, "--conservative") == 0) {
conservative = true;
filename = argv[2];
}
uint8_t *data;
size_t len = read_file(filename, &data);
parse_elf(data, len);
assert(cs_open(CS_ARCH_MIPS, (cs_mode)(CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN), &handle) == CS_ERR_OK);
cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
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);
cs_close(&handle);
}