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