1206 lines
26 KiB
C++
1206 lines
26 KiB
C++
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "decompiler.h"
|
|
#include "code_analyzer.h"
|
|
#include "platform_util.h"
|
|
|
|
struct Block;
|
|
|
|
#define PCM_BRANCH_TARGET (1<<0)
|
|
#define PCM_OPCODE (1<<1)
|
|
#define PCM_HAS_BLOCK (1<<2)
|
|
#define PCM_NUM_BITS 3
|
|
|
|
#define NEXT_OP(cip, op) cip += (dc->opdef[op].params + 1) * sizeof(cell_t)
|
|
|
|
struct Edge
|
|
{
|
|
Edge *next;
|
|
Block *target;
|
|
uint32_t ip;
|
|
};
|
|
|
|
struct Block
|
|
{
|
|
cell_t ip_start;
|
|
cell_t ip_end;
|
|
Edge *edge_list;
|
|
Edge *final_edge;
|
|
Block *next_in_table;
|
|
};
|
|
|
|
struct ControlFlowGraph
|
|
{
|
|
int err;
|
|
unsigned int max_blocks;
|
|
unsigned int max_edges;
|
|
unsigned int used_blocks;
|
|
unsigned int used_edges;
|
|
Block *blocks;
|
|
Edge *edges;
|
|
Block *entry;
|
|
FunctionInfo *func;
|
|
};
|
|
|
|
DefineNode::DefineNode(BaseNode *o, const char *fmt, ...) : BaseNode(_OP_DEFINE), other(o)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
Sp_FormatArgs(name, sizeof(name), fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static FunctionInfo *sp_InferFuncPrototype(sp_decomp_t *dc, uint32_t code_addr, bool is_public)
|
|
{
|
|
sp_tag_t *pTag;
|
|
sp_file_t *plugin;
|
|
FunctionInfo *info;
|
|
sp_fdbg_symbol_t *sym;
|
|
|
|
plugin = dc->plugin;
|
|
info = new FunctionInfo;
|
|
memset(info, 0, sizeof(FunctionInfo));
|
|
|
|
info->code_addr = code_addr;
|
|
|
|
if ((sym = Sp_FindFunctionSym(plugin, code_addr)) != NULL)
|
|
{
|
|
Sp_Format(info->name, sizeof(info->name), "%s", plugin->debug.stringbase + sym->name);
|
|
if (sym->tagid != 0)
|
|
{
|
|
info->tag = Sp_FindTag(plugin, sym->tagid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Sp_Format(info->name, sizeof(info->name), "function_%x", code_addr);
|
|
for (uint32_t i = 0; i < plugin->num_publics; i++)
|
|
{
|
|
if (plugin->publics[i].code_offs == code_addr)
|
|
{
|
|
Sp_Format(info->name,
|
|
sizeof(info->name),
|
|
"%s",
|
|
plugin->publics[i].name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
info->sym = sym;
|
|
info->is_public = is_public;
|
|
|
|
|
|
/* Search for all locals */
|
|
{
|
|
uint32_t i;
|
|
uint8_t *cursor;
|
|
|
|
cursor = (uint8_t *)plugin->debug.symbols;
|
|
for (i = 0; i < plugin->debug.syms_num; i++)
|
|
{
|
|
sym = (sp_fdbg_symbol_t *)cursor;
|
|
|
|
if (sym->ident == SP_SYM_VARIABLE
|
|
|| sym->ident == SP_SYM_REFARRAY
|
|
|| sym->ident == SP_SYM_REFERENCE
|
|
|| sym->ident == SP_SYM_ARRAY)
|
|
{
|
|
if (code_addr >= sym->codestart
|
|
&& code_addr < sym->codeend
|
|
&& sym->addr >= 0xC
|
|
&& (sym->addr & 0x3) == 0
|
|
&& ((sym->addr - 0xC) >> 2) < SP_MAX_EXEC_PARAMS
|
|
&& sym->vclass == 1)
|
|
{
|
|
unsigned int num;
|
|
|
|
num = (sym->addr - 0xC) >> 2;
|
|
info->known_args[num].sym = sym;
|
|
if (num + 1 > info->num_known_args)
|
|
{
|
|
info->num_known_args = num + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
cursor += sizeof(sp_fdbg_symbol_t);
|
|
if (sym->dimcount > 0)
|
|
{
|
|
cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Map all arguments into place. */
|
|
for (unsigned int i = 0; i < info->num_known_args; i++)
|
|
{
|
|
if ((sym = info->known_args[i].sym) == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
if (sym->tagid != 0 && (pTag = Sp_FindTag(plugin, sym->tagid)) != NULL)
|
|
{
|
|
info->known_args[i].tag = pTag;
|
|
}
|
|
info->known_args[i].name = plugin->debug.stringbase + sym->name;
|
|
|
|
info->known_args[i].dimcount = sym->dimcount;
|
|
if (sym->dimcount > 0)
|
|
{
|
|
info->known_args[i].dims =
|
|
(sp_fdbg_arraydim_t *)((char *)sym + sizeof(sp_fdbg_symbol_t));
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
static FunctionInfo *sp_GetFuncPrototype(sp_decomp_t *dc,
|
|
uint32_t code_addr,
|
|
bool known,
|
|
bool is_public = false);
|
|
static FunctionInfo *sp_GetFuncPrototype(sp_decomp_t *dc,
|
|
uint32_t code_addr,
|
|
bool known,
|
|
bool is_public)
|
|
{
|
|
FunctionInfo *func;
|
|
|
|
func = dc->funcs;
|
|
while (func != NULL)
|
|
{
|
|
if (func->code_addr == code_addr)
|
|
{
|
|
return func;
|
|
}
|
|
func = func->next;
|
|
}
|
|
|
|
/* See if this is public. */
|
|
if (!known)
|
|
{
|
|
is_public = false;
|
|
for (unsigned int i = 0; i < dc->plugin->num_publics; i++)
|
|
{
|
|
if (dc->plugin->publics[i].code_offs == code_addr)
|
|
{
|
|
is_public = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
func = sp_InferFuncPrototype(dc, code_addr, is_public);
|
|
func->next = dc->funcs;
|
|
dc->funcs = func;
|
|
|
|
return func;
|
|
}
|
|
|
|
static void sp_CacheLocals(sp_decomp_t *dc, FunctionInfo *info)
|
|
{
|
|
sp_file_t *plugin;
|
|
sp_fdbg_symbol_t *sym;
|
|
|
|
plugin = dc->plugin;
|
|
|
|
/* Search for all locals */
|
|
{
|
|
uint32_t i;
|
|
uint8_t *cursor;
|
|
|
|
cursor = (uint8_t *)plugin->debug.symbols;
|
|
for (i = 0; i < plugin->debug.syms_num; i++)
|
|
{
|
|
sym = (sp_fdbg_symbol_t *)cursor;
|
|
|
|
if ((sym->codestart >= info->code_addr && sym->codestart < info->code_end)
|
|
&& (sym->codeend <= info->code_end && sym->codeend >= info->code_addr)
|
|
&& sym->vclass == 1)
|
|
{
|
|
info->num_known_vars++;
|
|
}
|
|
|
|
cursor += sizeof(sp_fdbg_symbol_t);
|
|
if (sym->dimcount > 0)
|
|
{
|
|
cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Map all locals into place. */
|
|
if (info->num_known_vars)
|
|
{
|
|
uint32_t i, num;
|
|
uint8_t *cursor;
|
|
|
|
num = 0;
|
|
info->known_vars = new FuncVar[info->num_known_vars];
|
|
memset(info->known_vars, 0, sizeof(FuncVar) * info->num_known_vars);
|
|
|
|
cursor = (uint8_t *)plugin->debug.symbols;
|
|
for (i = 0; i < plugin->debug.syms_num; i++)
|
|
{
|
|
sym = (sp_fdbg_symbol_t *)cursor;
|
|
|
|
if (sym->ident == SP_SYM_VARIABLE
|
|
|| sym->ident == SP_SYM_REFARRAY
|
|
|| sym->ident == SP_SYM_REFERENCE
|
|
|| sym->ident == SP_SYM_ARRAY)
|
|
{
|
|
/* Detect all function variables. */
|
|
if ((sym->codestart >= info->code_addr && sym->codestart < info->code_end)
|
|
&& (sym->codeend <= info->code_end && sym->codeend >= info->code_addr)
|
|
&& sym->vclass == 1)
|
|
{
|
|
assert(num < info->num_known_vars);
|
|
info->known_vars[num].sym = sym;
|
|
info->known_vars[num].name = plugin->debug.stringbase + sym->name;
|
|
info->known_vars[num].tag =
|
|
(sym->tagid == 0) ? NULL : Sp_FindTag(plugin, sym->tagid);
|
|
info->known_vars[num].dimcount = sym->dimcount;
|
|
info->known_vars[num].dims =
|
|
(sym->dimcount == 0)
|
|
? NULL
|
|
: (sp_fdbg_arraydim_t *)(cursor + sizeof(sp_fdbg_symbol_t));
|
|
num++;
|
|
}
|
|
}
|
|
|
|
cursor += sizeof(sp_fdbg_symbol_t);
|
|
if (sym->dimcount > 0)
|
|
{
|
|
cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
|
|
}
|
|
}
|
|
|
|
assert(num == info->num_known_vars);
|
|
}
|
|
}
|
|
|
|
static void sp_PrintFunctionPrototype(PrintBuffer *printer, sp_decomp_t *dc, FunctionInfo *func)
|
|
{
|
|
FuncVar *var;
|
|
|
|
if (func->is_public)
|
|
{
|
|
printer->Append("public ");
|
|
}
|
|
if (func->tag != NULL)
|
|
{
|
|
printer->Append("%s:", func->tag->name);
|
|
}
|
|
printer->Append("%s(", func->name);
|
|
|
|
for (unsigned int i = 0; i < func->num_known_args; i++)
|
|
{
|
|
var = &func->known_args[i];
|
|
if (var->sym == NULL)
|
|
{
|
|
printer->Append("unknown_arg_%d%s, ", i, i == func->num_known_args - 1 ? "" : ",");
|
|
continue;
|
|
}
|
|
if (var->sym->ident == SP_SYM_REFERENCE)
|
|
{
|
|
printer->Append("&");
|
|
}
|
|
if (var->tag != NULL)
|
|
{
|
|
printer->Append("%s:", var->tag->name);
|
|
}
|
|
printer->Append("%s", var->name);
|
|
|
|
if (var->sym->dimcount > 0)
|
|
{
|
|
sp_fdbg_arraydim_t *dims =
|
|
(sp_fdbg_arraydim_t *)((char *)var->sym + sizeof(sp_fdbg_symbol_t));
|
|
|
|
for (uint16_t j = 0; j < var->sym->dimcount; j++)
|
|
{
|
|
if (dims[j].size == 0)
|
|
{
|
|
printer->Append("[]");
|
|
}
|
|
else
|
|
{
|
|
printer->Append("[%d]", dims[j].size);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i != func->num_known_args - 1)
|
|
{
|
|
printer->Append(", ");
|
|
}
|
|
}
|
|
|
|
printer->Append(")");
|
|
}
|
|
|
|
void sp_DebugExprTree(PrintBuffer *buffer, BaseNode *node, bool is_stmt=false)
|
|
{
|
|
switch (node->op)
|
|
{
|
|
case _OP_DEFINE:
|
|
{
|
|
DefineNode *def = (DefineNode *)node;
|
|
|
|
buffer->Append("new %s = ", def->name);
|
|
sp_DebugExprTree(buffer, def->other, is_stmt);
|
|
break;
|
|
}
|
|
case OP_CALL:
|
|
{
|
|
CallNode *call = (CallNode *)node;
|
|
|
|
buffer->Append("%s(", call->func->name);
|
|
for (unsigned int i = 0; i < call->argc; i++)
|
|
{
|
|
sp_DebugExprTree(buffer, call->args[i], false);
|
|
}
|
|
buffer->Append(")");
|
|
break;
|
|
}
|
|
case _OP_USE:
|
|
{
|
|
UseNode *unode = (UseNode *)node;
|
|
|
|
buffer->Append("%s", unode->def->name);
|
|
break;
|
|
}
|
|
case _OP_VAR:
|
|
{
|
|
VarNode *var_node = (VarNode *)node;
|
|
buffer->Append("%s", var_node->var->name);
|
|
break;
|
|
}
|
|
case _OP_NEW:
|
|
{
|
|
DeclNode *d_node = (DeclNode *)node;
|
|
|
|
if (d_node->expr == NULL)
|
|
{
|
|
buffer->Append("decl ");
|
|
sp_DebugExprTree(buffer, d_node->var, true);
|
|
}
|
|
else
|
|
{
|
|
buffer->Append("new ");
|
|
sp_DebugExprTree(buffer, d_node->var, true);
|
|
buffer->Append(" = ");
|
|
sp_DebugExprTree(buffer, d_node->expr, true);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case _OP_STMT:
|
|
case OP_RETN:
|
|
{
|
|
StmtNode *u_node = (StmtNode *)node;
|
|
|
|
while (u_node != NULL)
|
|
{
|
|
buffer->Append("\t");
|
|
if (u_node->op == OP_RETN)
|
|
{
|
|
buffer->Append("return ");
|
|
}
|
|
sp_DebugExprTree(buffer, u_node->node, true);
|
|
buffer->Append(";\n");
|
|
u_node = u_node->next;
|
|
}
|
|
break;
|
|
}
|
|
case _OP_CONST:
|
|
{
|
|
ConstNode *c_node = (ConstNode *)node;
|
|
|
|
buffer->Append("%d", c_node->val);
|
|
|
|
break;
|
|
}
|
|
case OP_ADD:
|
|
case OP_ADD_C:
|
|
case OP_SMUL:
|
|
case OP_SMUL_C:
|
|
case OP_SDIV:
|
|
case OP_SUB_ALT:
|
|
case _OP_STORE:
|
|
case _OP_MODULO:
|
|
{
|
|
BinOpNode *bin_node = (BinOpNode *)node;
|
|
|
|
if (!is_stmt)
|
|
{
|
|
buffer->Append("(");
|
|
}
|
|
sp_DebugExprTree(buffer, bin_node->left);
|
|
|
|
switch (node->op)
|
|
{
|
|
case OP_ADD:
|
|
case OP_ADD_C:
|
|
{
|
|
buffer->Append(" + ");
|
|
break;
|
|
}
|
|
case OP_SMUL:
|
|
case OP_SMUL_C:
|
|
{
|
|
buffer->Append(" * ");
|
|
break;
|
|
}
|
|
case _OP_STORE:
|
|
{
|
|
buffer->Append(" = ");
|
|
break;
|
|
}
|
|
case OP_SUB_ALT:
|
|
{
|
|
buffer->Append(" - ");
|
|
break;
|
|
}
|
|
case OP_SDIV:
|
|
{
|
|
buffer->Append(" / ");
|
|
break;
|
|
}
|
|
case _OP_MODULO:
|
|
{
|
|
buffer->Append(" % ");
|
|
break;
|
|
}
|
|
}
|
|
|
|
sp_DebugExprTree(buffer, bin_node->right, node->op == _OP_STORE);
|
|
if (!is_stmt)
|
|
{
|
|
buffer->Append(")");
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void sp_DebugPrint(FunctionInfo *info, sp_decomp_t *dc, BaseNode *node)
|
|
{
|
|
PrintBuffer buffer;
|
|
|
|
sp_PrintFunctionPrototype(&buffer, dc, info);
|
|
buffer.Append("\n{\n");
|
|
sp_DebugExprTree(&buffer, node, true);
|
|
buffer.Append("}\n");
|
|
buffer.Dump(stdout);
|
|
}
|
|
|
|
#define PCODE_VAL(pcode, offs) *(cell_t *)((char *)(pcode)+(offs))
|
|
#define PCODE_PARAM(pcode, offs, num) *(cell_t *)((char *)(pcode)+(offs)+((num)*sizeof(cell_t)))
|
|
|
|
FuncVar *sp_FindGraphVar(FunctionInfo *func, ucell_t cip, cell_t sp, cell_t stack)
|
|
{
|
|
if (stack >= 0xC)
|
|
{
|
|
if ((stack & 0x3) != 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
stack -= 0xC;
|
|
stack >>= 2;
|
|
if (stack >= SP_MAX_EXEC_PARAMS)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (func->known_args[stack].sym == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
return &func->known_args[stack];
|
|
}
|
|
else if (stack < 0)
|
|
{
|
|
FuncVar *var;
|
|
|
|
if (stack < sp)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < func->num_known_vars; i++)
|
|
{
|
|
var = &func->known_vars[i];
|
|
|
|
if (cip >= var->sym->codestart
|
|
&& cip <= var->sym->codeend
|
|
&& stack == var->sym->addr)
|
|
{
|
|
return var;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#define CHECK_VALUE(v) \
|
|
assert(v != NULL); \
|
|
if (v == NULL) { \
|
|
err = SP_ERROR_INVALID_INSTRUCTION; \
|
|
goto return_error; \
|
|
}
|
|
|
|
void *operator new(size_t size, ControlFlowGraph *graph)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
void *operator new[](size_t size, ControlFlowGraph *graph)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
void operator delete(void *mem, ControlFlowGraph *graph)
|
|
{
|
|
free(mem);
|
|
}
|
|
|
|
void operator delete[](void *mem, ControlFlowGraph *graph)
|
|
{
|
|
free(mem);
|
|
}
|
|
|
|
#define MAX_STACK_ENTRIES 256
|
|
|
|
struct StackEntry
|
|
{
|
|
StackEntry()
|
|
{
|
|
}
|
|
StackEntry(cell_t d, BaseNode *e) : depth(d), expr(e)
|
|
{
|
|
}
|
|
cell_t depth;
|
|
BaseNode *expr;
|
|
};
|
|
|
|
int sp_AnalyzeGraph(sp_decomp_t *dc, ControlFlowGraph *graph)
|
|
{
|
|
int err;
|
|
cell_t sp;
|
|
Block *block;
|
|
cell_t *pcode;
|
|
cell_t cip, cip_end;
|
|
StmtNode *root, *rtemp;
|
|
StmtNode *root_tail;
|
|
BaseNode *pri, *alt;
|
|
FunctionInfo *func, *tfunc;
|
|
cell_t p1, p2, op;
|
|
FuncVar *v1, *v2;
|
|
unsigned int callnumber = 0;
|
|
unsigned int stack_entries = 0;
|
|
StackEntry eval_stack[MAX_STACK_ENTRIES];
|
|
|
|
/* Init everything.
|
|
* Stack pointer starts off at 0.
|
|
*/
|
|
sp = 0;
|
|
pri = NULL;
|
|
alt = NULL;
|
|
root = NULL;
|
|
func = graph->func;
|
|
block = graph->entry;
|
|
pcode = (cell_t *)dc->plugin->pcode;
|
|
|
|
cip = block->ip_start;
|
|
cip_end = block->ip_end;
|
|
|
|
root_tail = root = new (graph) StmtNode(_OP_STMT, NULL, NULL);
|
|
|
|
while (cip < cip_end)
|
|
{
|
|
op = PCODE_VAL(pcode, cip);
|
|
|
|
switch (op)
|
|
{
|
|
case OP_PROC:
|
|
case OP_BREAK:
|
|
{
|
|
break;
|
|
}
|
|
case OP_CALL:
|
|
case OP_SYSREQ_N:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
if (op == OP_CALL)
|
|
{
|
|
tfunc = sp_GetFuncPrototype(dc, p1, false);
|
|
}
|
|
else if (op == OP_SYSREQ_N)
|
|
{
|
|
assert(dc->natives);
|
|
assert(uint32_t(p1) < dc->plugin->num_natives);
|
|
tfunc = &dc->natives[p1];
|
|
}
|
|
CHECK_VALUE(tfunc);
|
|
|
|
/* Sanity checks, :TODO: runtime checks later. */
|
|
assert(stack_entries >= 1);
|
|
|
|
if (op == OP_SYSREQ_N)
|
|
{
|
|
p2 = PCODE_PARAM(pcode, cip, 2);
|
|
}
|
|
else
|
|
{
|
|
/* Try to read the value off the stack. */
|
|
ConstNode *cnode;
|
|
cnode = (ConstNode *)eval_stack[stack_entries-1].expr;
|
|
/* Sanity checks, :TODO: runtime checks later. */
|
|
assert(cnode->op == _OP_CONST);
|
|
assert(cnode->val >= 0);
|
|
assert(uint32_t(cnode->val) == tfunc->num_known_args);
|
|
p2 = cnode->val;
|
|
stack_entries--;
|
|
}
|
|
|
|
assert(uint32_t(p2) <= stack_entries);
|
|
|
|
/* Build arg SSA */
|
|
BaseNode **nodes = new (graph) BaseNode *[p2];
|
|
for (cell_t argno = 0; argno < p2; argno++, stack_entries--)
|
|
{
|
|
nodes[argno] = eval_stack[stack_entries-1].expr;
|
|
}
|
|
|
|
/* Get rid of any args we pushed. */
|
|
sp += p2 * sizeof(cell_t);
|
|
if (op == OP_CALL)
|
|
{
|
|
sp += sizeof(cell_t);
|
|
}
|
|
assert(sp <= 0);
|
|
|
|
/* Build function call SSA */
|
|
pri = new (graph) CallNode(tfunc, nodes, p2);
|
|
pri = new (graph) DefineNode(pri, "call%03x", ++callnumber);
|
|
rtemp = new (graph) StmtNode(_OP_STMT, pri, NULL);
|
|
root_tail->next = rtemp;
|
|
root_tail = rtemp;
|
|
pri = new (graph) UseNode((DefineNode *)pri);
|
|
|
|
break;
|
|
}
|
|
case OP_CONST_PRI:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
pri = new (graph) ConstNode(p1);
|
|
break;
|
|
}
|
|
case OP_PUSH_C:
|
|
case OP_PUSH2_C:
|
|
case OP_PUSH3_C:
|
|
case OP_PUSH5_C:
|
|
{
|
|
for (int N = 1; N <= dc->opdef[op].params; N++)
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, N);
|
|
|
|
/* Adjust stack pointer. */
|
|
sp -= sizeof(cell_t);
|
|
|
|
if ((v1 = sp_FindGraphVar(func, cip, sp, sp)) == NULL)
|
|
{
|
|
/* Add a new stack entry. */
|
|
assert(stack_entries < MAX_STACK_ENTRIES);
|
|
eval_stack[stack_entries++] = StackEntry(sp, new ConstNode(p1));
|
|
}
|
|
else
|
|
{
|
|
assert(v1->new_stmt == NULL);
|
|
if (v1->new_stmt == NULL)
|
|
{
|
|
v1->new_stmt = new (graph) DeclNode(
|
|
new (graph) VarNode(v1),
|
|
new (graph) ConstNode(p1));
|
|
rtemp = new (graph) StmtNode(
|
|
_OP_STMT,
|
|
v1->new_stmt,
|
|
NULL);
|
|
root_tail->next = rtemp;
|
|
root_tail = rtemp;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OP_PUSH_S:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
v1 = sp_FindGraphVar(func, cip, sp, p1);
|
|
CHECK_VALUE(v1);
|
|
|
|
/* Adjust stack pointer. */
|
|
sp -= sizeof(cell_t);
|
|
|
|
/* Right now, don't allow aliasing or overflows. */
|
|
assert(sp_FindGraphVar(func, cip, sp, sp) == NULL);
|
|
assert(stack_entries < MAX_STACK_ENTRIES);
|
|
|
|
/* Add a new stack entry. */
|
|
eval_stack[stack_entries++] = StackEntry(sp, new (graph) VarNode(v1));
|
|
break;
|
|
}
|
|
case OP_PUSH_PRI:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
|
|
/* Adjust stack pointer. */
|
|
sp -= sizeof(cell_t);
|
|
|
|
/* Right now, don't allow aliasing or overflows. */
|
|
assert(sp_FindGraphVar(func, cip, sp, sp) == NULL);
|
|
assert(stack_entries < MAX_STACK_ENTRIES);
|
|
|
|
/* Add a new stack entry. */
|
|
eval_stack[stack_entries++] = StackEntry(sp, pri);
|
|
break;
|
|
}
|
|
case OP_POP_ALT:
|
|
{
|
|
assert(stack_entries > 0);
|
|
assert(eval_stack[stack_entries - 1].depth == sp);
|
|
|
|
alt = eval_stack[--stack_entries].expr;
|
|
sp += sizeof(cell_t);
|
|
assert(sp <= 0);
|
|
break;
|
|
}
|
|
case OP_STACK:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
sp += p1;
|
|
assert(sp <= 0);
|
|
if (p1 < 0)
|
|
{
|
|
/* Find if we should track a new variable declaration. */
|
|
v1 = sp_FindGraphVar(func, cip, sp, sp);
|
|
CHECK_VALUE(v1);
|
|
assert(v1->new_stmt == NULL);
|
|
if (v1->new_stmt == NULL)
|
|
{
|
|
v1->new_stmt = new (graph) DeclNode(
|
|
new (graph) VarNode(v1),
|
|
NULL);
|
|
rtemp = new (graph) StmtNode(
|
|
_OP_STMT,
|
|
v1->new_stmt,
|
|
NULL);
|
|
root_tail->next = rtemp;
|
|
root_tail = rtemp;
|
|
}
|
|
assert(abs(p1) >> 2 == 1);
|
|
}
|
|
else
|
|
{
|
|
/* Erase anything left over on the evaluation stack. */
|
|
while (stack_entries > 0
|
|
&& eval_stack[stack_entries - 1].depth < sp)
|
|
{
|
|
assert(0);
|
|
stack_entries--;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OP_ZERO_PRI:
|
|
{
|
|
pri = new (graph) ConstNode(0);
|
|
break;
|
|
}
|
|
case OP_MOVE_ALT:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
alt = pri;
|
|
break;
|
|
}
|
|
case OP_LOAD_S_PRI:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
v1 = sp_FindGraphVar(func, cip, sp, p1);
|
|
CHECK_VALUE(v1);
|
|
pri = new (graph) VarNode(v1);
|
|
break;
|
|
}
|
|
case OP_LOAD_S_ALT:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
v1 = sp_FindGraphVar(func, cip, sp, p1);
|
|
CHECK_VALUE(v1);
|
|
alt = new (graph) VarNode(v1);
|
|
break;
|
|
}
|
|
case OP_STOR_S_PRI:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
v1 = sp_FindGraphVar(func, cip, sp, p1);
|
|
CHECK_VALUE(v1);
|
|
CHECK_VALUE(pri);
|
|
rtemp = new (graph) StmtNode(
|
|
_OP_STMT,
|
|
new (graph) BinOpNode(_OP_STORE, new (graph) VarNode(v1), pri),
|
|
NULL);
|
|
root_tail->next = rtemp;
|
|
root_tail = rtemp;
|
|
|
|
/**
|
|
* We create a varnode to simulate a load here.
|
|
* This helps accidental duplication of the expression tree.
|
|
*/
|
|
pri = new (graph) VarNode(v1);
|
|
break;
|
|
}
|
|
case OP_LOAD_S_BOTH:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
p2 = PCODE_PARAM(pcode, cip, 2);
|
|
v1 = sp_FindGraphVar(func, cip, sp, p1);
|
|
v2 = sp_FindGraphVar(func, cip, sp, p2);
|
|
CHECK_VALUE(v1);
|
|
CHECK_VALUE(v2);
|
|
pri = new (graph) VarNode(v1);
|
|
alt = new (graph) VarNode(v2);
|
|
break;
|
|
}
|
|
case OP_ADD:
|
|
case OP_SMUL:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
CHECK_VALUE(alt);
|
|
pri = new (graph) BinOpNode(op, pri, alt);
|
|
break;
|
|
}
|
|
case OP_SUB_ALT:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
CHECK_VALUE(alt);
|
|
pri = new (graph) BinOpNode(op, alt, pri);
|
|
break;
|
|
}
|
|
case OP_SDIV:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
CHECK_VALUE(alt);
|
|
pri = new (graph) BinOpNode(OP_SDIV, pri, alt);
|
|
alt = new (graph) BinOpNode(_OP_MODULO, pri, alt);
|
|
break;
|
|
}
|
|
case OP_SDIV_ALT:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
CHECK_VALUE(alt);
|
|
pri = new (graph) BinOpNode(OP_SDIV, alt, pri);
|
|
alt = new (graph) BinOpNode(_OP_MODULO, alt, pri);
|
|
break;
|
|
}
|
|
case OP_ADD_C:
|
|
case OP_SMUL_C:
|
|
{
|
|
p1 = PCODE_PARAM(pcode, cip, 1);
|
|
CHECK_VALUE(pri);
|
|
pri = new (graph) BinOpNode(op, pri, new (graph) ConstNode(p1));
|
|
break;
|
|
}
|
|
case OP_RETN:
|
|
{
|
|
CHECK_VALUE(pri);
|
|
rtemp = new (graph) StmtNode(op, pri, NULL);
|
|
root_tail->next = rtemp;
|
|
root_tail = rtemp;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NEXT_OP(cip, op);
|
|
}
|
|
|
|
sp_DebugPrint(graph->func, dc, root->next);
|
|
|
|
return SP_ERROR_NONE;
|
|
|
|
return_error:
|
|
return err;
|
|
}
|
|
|
|
#undef CHECK_FUNCVAR
|
|
|
|
inline Block *sp_BlockFromMap(sp_decomp_t *dc, ControlFlowGraph *graph, cell_t cip)
|
|
{
|
|
cell_t val;
|
|
|
|
assert(PCODE_VAL(dc->pcode_map, cip) & PCM_HAS_BLOCK);
|
|
|
|
val = PCODE_VAL(dc->pcode_map, cip);
|
|
val >>= PCM_NUM_BITS;
|
|
|
|
assert(val >= 0 && val < (cell_t)graph->used_blocks);
|
|
|
|
return &graph->blocks[val];
|
|
}
|
|
|
|
void sp_InsertBlock(sp_decomp_t *dc, unsigned int index, cell_t cip)
|
|
{
|
|
cell_t val;
|
|
|
|
val = PCODE_VAL(dc->pcode_map, cip);
|
|
|
|
assert(!(val & PCM_HAS_BLOCK));
|
|
assert(index < 0x20000000);
|
|
|
|
val |= PCM_HAS_BLOCK;
|
|
val |= index << 3;
|
|
|
|
PCODE_VAL(dc->pcode_map, cip) = val;
|
|
}
|
|
|
|
static Block *sp_BuildBlocks(sp_decomp_t *dc, ControlFlowGraph *graph, cell_t cip)
|
|
{
|
|
cell_t op;
|
|
bool is_branch;
|
|
cell_t target;
|
|
Block *block;
|
|
Edge *edge_tail;
|
|
Block *outgoing;
|
|
cell_t cip_end;
|
|
cell_t *pcode;
|
|
|
|
target = 0;
|
|
cip_end = graph->func->code_end;
|
|
pcode = (cell_t *)dc->plugin->pcode;
|
|
|
|
if (PCODE_VAL(dc->pcode_map, cip) & PCM_HAS_BLOCK)
|
|
{
|
|
return sp_BlockFromMap(dc, graph, cip);
|
|
}
|
|
|
|
/* We can't add a new block if we hit our limit. */
|
|
assert(graph->used_blocks < graph->max_blocks);
|
|
if (graph->used_blocks >= graph->max_blocks)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
edge_tail = NULL;
|
|
|
|
/* Reserve a new block. */
|
|
block = &graph->blocks[graph->used_blocks++];
|
|
block->ip_start = cip;
|
|
|
|
/* Enter us into the map. */
|
|
sp_InsertBlock(dc, graph->used_blocks - 1, cip);
|
|
|
|
/* Walk the code. */
|
|
while (cip < cip_end)
|
|
{
|
|
/* Check whether this is the start of a new block. */
|
|
if (cip != block->ip_start
|
|
&& (PCODE_VAL(dc->pcode_map, cip) & PCM_BRANCH_TARGET))
|
|
{
|
|
block->ip_end = cip;
|
|
if ((outgoing = sp_BuildBlocks(dc, graph, cip)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* :TODO: add edge */
|
|
return block;
|
|
}
|
|
|
|
is_branch = false;
|
|
op = PCODE_VAL(pcode, cip);
|
|
NEXT_OP(cip, op);
|
|
|
|
if (is_branch)
|
|
{
|
|
/* Check that branch offsets go to valid instructions. */
|
|
if (!(PCODE_VAL(dc->pcode_map, target) & PCM_OPCODE))
|
|
{
|
|
graph->err = SP_ERROR_INVALID_INSTRUCTION;
|
|
return NULL;
|
|
}
|
|
|
|
/* Get our target block. */
|
|
if ((outgoing = sp_BuildBlocks(dc, graph, target)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* :TODO: add edge */
|
|
}
|
|
else if (op == OP_RETN /* :TODO: || op == OP_RET*/)
|
|
{
|
|
block->ip_end = cip;
|
|
return block;
|
|
}
|
|
}
|
|
|
|
graph->err = SP_ERROR_INVALID_INSTRUCTION;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int Sp_DecompFunction(sp_decomp_t *dc, uint32_t code_addr, bool is_public)
|
|
{
|
|
int err;
|
|
cell_t op;
|
|
ucell_t cip;
|
|
cell_t *pcode;
|
|
ucell_t cip_end;
|
|
sp_file_t *plugin;
|
|
FunctionInfo *func;
|
|
ControlFlowGraph graph;
|
|
|
|
func = NULL;
|
|
plugin = dc->plugin;
|
|
memset(&graph, 0, sizeof(graph));
|
|
|
|
if (code_addr >= plugin->pcode_size)
|
|
{
|
|
err = SP_ERROR_INVALID_INSTRUCTION;
|
|
goto return_error;
|
|
}
|
|
|
|
pcode = (cell_t *)plugin->pcode;
|
|
|
|
if (PCODE_VAL(pcode, code_addr) != OP_PROC)
|
|
{
|
|
err = SP_ERROR_INVALID_INSTRUCTION;
|
|
goto return_error;
|
|
}
|
|
|
|
cip = code_addr + sizeof(cell_t);
|
|
|
|
/* See if we have an existing function stored. */
|
|
func = sp_GetFuncPrototype(dc, code_addr, true, is_public);
|
|
|
|
while (cip < plugin->pcode_size)
|
|
{
|
|
op = PCODE_VAL(pcode, cip);
|
|
if (op >= OP_TOTAL || op < 0)
|
|
{
|
|
err = SP_ERROR_INVALID_INSTRUCTION;
|
|
}
|
|
if (dc->opdef[op].name == NULL)
|
|
{
|
|
err = SP_ERROR_INVALID_INSTRUCTION;
|
|
}
|
|
if (op == OP_PROC)
|
|
{
|
|
break;
|
|
}
|
|
NEXT_OP(cip, op);
|
|
}
|
|
cip_end = cip;
|
|
|
|
func->code_end = cip_end;
|
|
|
|
/* Now we can cache all local vars. */
|
|
sp_CacheLocals(dc, func);
|
|
|
|
/**
|
|
* Make the entry point as the first block.
|
|
* It needs to be flagged as well so it doesn't get double counted.
|
|
*/
|
|
memset(&PCODE_VAL(dc->pcode_map, code_addr), 0, cip_end - code_addr);
|
|
|
|
graph.max_blocks = 1;
|
|
graph.func = func;
|
|
PCODE_VAL(dc->pcode_map, code_addr) |= PCM_BRANCH_TARGET;
|
|
|
|
/* Walk the code. */
|
|
{
|
|
cell_t target;
|
|
bool is_branch;
|
|
|
|
target = 0;
|
|
cip = code_addr;
|
|
while (cip < cip_end)
|
|
{
|
|
/* Get opcode */
|
|
op = PCODE_VAL(pcode, cip);
|
|
/* Mark as not a branch */
|
|
is_branch = false;
|
|
/* Make sure we know this is a valid target */
|
|
PCODE_VAL(dc->pcode_map, cip) |= PCM_OPCODE;
|
|
|
|
if (is_branch)
|
|
{
|
|
/**
|
|
* Keep track of where branches are and how many blocks we need.
|
|
*/
|
|
if (!(PCODE_VAL(dc->pcode_map, target) & PCM_BRANCH_TARGET))
|
|
{
|
|
PCODE_VAL(dc->pcode_map, target) |= PCM_BRANCH_TARGET;
|
|
graph.max_blocks++;
|
|
|
|
/**
|
|
* If we're creating a new block, estimate that we need to create a new edge
|
|
* as well.
|
|
*/
|
|
graph.max_edges++;
|
|
}
|
|
|
|
/**
|
|
* In this algorithm, jumps do not terminate a block unless they are unconditional.
|
|
* This reduces the number of blocks and edges.
|
|
*
|
|
* A property of this is that jumps are edges, so the number of edges is identical
|
|
* to the number of jumps.
|
|
*/
|
|
graph.max_edges++;
|
|
}
|
|
|
|
NEXT_OP(cip, op);
|
|
}
|
|
}
|
|
|
|
/* Initialize memory needed for the graph. */
|
|
graph.blocks = new Block[graph.max_blocks];
|
|
memset(graph.blocks, 0, sizeof(Block) * graph.max_blocks);
|
|
|
|
if (graph.max_edges != 0)
|
|
{
|
|
graph.edges = new Edge[graph.max_edges];
|
|
memset(graph.edges, 0, sizeof(Edge) * graph.max_edges);
|
|
}
|
|
|
|
if ((graph.entry = sp_BuildBlocks(dc, &graph, code_addr)) == NULL)
|
|
{
|
|
if ((err = graph.err) == SP_ERROR_NONE)
|
|
{
|
|
err = SP_ERROR_ABORTED;
|
|
}
|
|
goto return_error;
|
|
}
|
|
|
|
if ((err = sp_AnalyzeGraph(dc, &graph)) != SP_ERROR_NONE)
|
|
{
|
|
goto return_error;
|
|
}
|
|
|
|
return SP_ERROR_NONE;
|
|
|
|
return_error:
|
|
return err;
|
|
}
|