sourcemod/sourcepawn/decompiler/code_analyzer.cpp

1206 lines
26 KiB
C++
Raw Normal View History

#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;
}