/*
** $Id: disass.c,v 1.5 2012/06/10 13:06:37 dredd Exp $
**
** $Source: /cvsroot/swlpc/swlpc/modules/disass.c,v $
** $Revision: 1.5 $
** $Date: 2012/06/10 13:06:37 $
** $State: Exp $
**
** Author: Mike McGaughey & Geoff Wong, 1993-1999
**
** See the file "Copying" distributed with this file.
**
*/
/*
* Modifications for dbx, assembler, disassembler - mmcg
* I have made NO attempt to make any of the following fast; the
* idea was that it'd be useful for debugging objects (and designing
* optimisations). I removed the S_ from the opcode names because I expect
* that although we'll be compiling this sort of info to C one day, we'll
* need a serious assembler for it anyway (i.e. no easy #including - ho hum).
* The `parameter' info should be enough to construct an assembler as well -
* when save_object is fixed to save/restore to strings, we can use its
* string, int, etc format. Note that constructing an assembler would
* require us to sensibly integrate the info about the number of parameters to
* an efun, if only for error checking purposes, as parts of the driver
* rely on it (so it won't be here in a hurry).
*/
/*
* You can use the disassembler on its functions.
* Args: object, func name, first opcode, num opcodes.
* Returns new array. - mmcg
*/
#include <stdlib.h>
#include "stack_access.h"
#include "stack.h"
#include "../runtime/hash.h"
//#include "security.h"
struct otbl codes[] =
{
{"END", S_END, SA_NONE}, /* fn end */
{"PUSHC", S_PUSHCARGS, SA_INT8},
{"ADDIND", S_ADDIND, SA_NONE},
{"ADDSP", S_ADDSP, SA_INT8},
{"AGGREGATE", S_AGGREGATE, SA_NONE},
{"AND", S_AND, SA_NONE}, /* and_eq */
{"CALL", S_CALL, SA_SMCADDR},
{"CALLO", S_CALLO, SA_CSTRING},
{"CALL_OUT", S_CALL_OUT, SA_NONE},
{"CATCH", S_CATCH, SA_SMCR16},
{"COPY", S_COPY, SA_INT8},
{"PUSHG", S_PUSHG, SA_GLOBREF},
{"DIVIDE", S_DIVIDE, SA_NONE},
{"EFUN", S_EFUN, SA_EFUN},
{"EFUND", S_EFUND, SA_EFUN},
{"ENTER", S_ENTER, SA_INT8}, /* afterarg type checks on stack */
{"EQ", S_EQ, SA_NONE},
{"GE", S_GE, SA_NONE},
{"GT", S_GT, SA_NONE},
{"LT", S_LT, SA_NONE},
{"JNZERO", S_JNZERO, SA_SMCR16},
{"JUMP", S_JUMP, SA_SMCR16}, /* should be defined at compile time */
{"JUMPU", S_JUMPU, SA_SMCR16}, /* undefined (patched) */
{"JZERO", S_JZERO, SA_SMCR16},
{"LAND", S_LAND, SA_NONE},
{"LE", S_LE, SA_NONE},
{"LNOT", S_LNOT, SA_NONE},
{"LOR", S_LOR, SA_NONE},
{"LSH", S_LSH, SA_NONE},
{"MINUS", S_MINUS, SA_NONE},
{"MOD", S_MOD, SA_NONE},
{"POPG", S_POPG, SA_GLOBREF},
{"MULT", S_MULT, SA_NONE},
{"NE", S_NE, SA_NONE},
{"NEGATE", S_NEGATE, SA_NONE},
{"NOT", S_NOT, SA_NONE},
{"OR", S_OR, SA_NONE},
{"PLUS", S_PLUS, SA_NONE},
{"PUSH8", S_PUSH8, SA_INT8},
{"POPCATCH", S_POPCATCH, SA_NONE},
{"POPL", S_POPL, SA_LOCREF},
{"PUSH", S_PUSH, SA_VALPTR},
{"RETURN", S_RETURN, SA_NONE},
{"RSH", S_RSH, SA_NONE},
{"THROW", S_THROW, SA_NONE},
{"TYPEC", S_TYPEC, SA_TYI16},
{"XOR", S_XOR, SA_NONE},
{"PUSHL", S_PUSHL, SA_LOCREF},
{"CALLU", S_CALLU, SA_CSTRING}, /* undefined call */
// {"EXTRACT", S_EXTRACT, SA_NONE},
{"POPI", S_POPI, SA_NONE},
{"PUSHi", S_PUSHi, SA_INT32},
{"CALLOF", S_CALLOF, SA_NONE},
{"POPO", S_POPO, SA_NONE},
{"PUSHr", S_PUSHr, SA_REAL},
{"PUSHs", S_PUSHs, SA_CSTRING},
{"ARR_ALLOC", S_ARR_ALLOC, SA_NONE},
{"OFLOW_CHECK", S_OFLOW_CHECK, SA_NONE},
{"PUSHCOFF", S_PUSHCOFF, SA_INT32},
{"SWITCH", S_SWITCH, SA_INT8},
{0, 0, 0}
};
/*
* To be called only when disassembling a validly compiled object (it
* fatals the driver if it's not found).
*/
int find_ocode(int oc)
{
int x = 0;
while (codes[x].name)
{
if (codes[x].scode == oc)
break;
x++;
}
if (!codes[x].name)
fatal("Bad SM code\n");
return x;
}
/*
* Make a printable/parseable type name. Should not be called with a null buffer.
* This will probably change to suit the assembler, later.
* At the moment, it's C-like.
*/
int printable_type(char *buff, int x)
{
int y, f = 0;
for (y = T_NUM; y < 16; y++)
{
if ((1 << y) & x)
{
strcat(buff, typename(1 << y));
strcat(buff, " ");
}
}
for (y = 0; y < T_NUM; y++)
{
if ((1 << y) & x)
{
if (!f)
strcat(buff, "(");
else
strcat(buff, "|");
strcat(buff, typename(1 << y));
f = 1;
}
}
if (f)
strcat(buff, ")");
return f;
}
/*
* Given a function and code offset, disassemble the opcode there into a buffer.
* Returns length of the disassembled code. If buff is 0, assumes it's
* part of a preprocessing pass (and won't print to it).
* This loads the given code pointer int sPC, uses the stack machine
* to access the data, then restores the old sPC. This messy implementation
* is because geoff deals with the stack machine, and it may change in future.
*/
int printable_ocode(char *buff, Class * ob, Func * p, int offs)
{
byte *z, *oldPC = sPC;
int x;
byte *pc;
sPC = p->block + offs;
x = INST;
x = find_ocode(x);
if (buff)
sprintf(buff, "%s\t\t", codes[x].name);
switch (codes[x].argtype)
{
case SA_NONE:
if (buff)
buff[strlen(buff) - 2] = 0; /* delete the tab */
break;
case SA_EFUN:
x = (byte) INST;
if (buff)
strcat(buff, predef_by_num(x));
break;
case SA_SMCADDR:
pc = (unsigned char *) INST32;
if (buff)
{
#if 0
fun = find_func_header(ob, pc);
if (!fun)
sprintf(buff + strlen(buff), "(unknown fun): %x", (int) pc);
else
sprintf(buff + strlen(buff), "%s:%d", fun->name, pc - fun->block);
#endif
if (!pc)
sprintf(buff + strlen(buff), "(unknown fun)");
else
sprintf(buff + strlen(buff), "%s", ((Func *) pc)->name->str);
}
break;
case SA_SMCR16:
x = (short) INST16;
if (buff)
sprintf(buff + strlen(buff), "%s:%d", p->name->str, offs + x + 3);
break;
case SA_CSTRING:
x = INST32;
if (buff)
sprintf(buff + strlen(buff), "\"%s\"", (char *) x);
break;
case SA_INT8:
x = (char) INST;
if (buff)
sprintf(buff + strlen(buff), "%d", (int) x);
break;
case SA_INT16:
x = (short) INST16;
if (buff)
sprintf(buff + strlen(buff), "%d", (int) x);
break;
case SA_INT32:
x = INST32;
if (buff)
sprintf(buff + strlen(buff), "%d", x);
break;
case SA_REAL:
x = INST32;
if (buff)
sprintf(buff + strlen(buff), "%f", (float)x);
break;
case SA_TYI16:
x = INST16;
if (buff)
{
printable_type(buff + strlen(buff), x);
}
break;
case SA_GLOBREF:
x = (byte) INST;
if (buff)
{
sprintf(buff + strlen(buff), "%s\t; ", ob->global_table[x]->u.string->str);
printable_type(buff + strlen(buff), ob->global_table[x]->type);
}
break;
case SA_LOCREF:
x = (char) INST;
if (buff)
{
if (x < 0)
sprintf(buff + strlen(buff), "A.%d", p->num_arg + x);
else
sprintf(buff + strlen(buff), "L.%d", x);
}
break;
case SA_VALPTR:
x = INST32;
if (buff)
sprintf(buff + strlen(buff), "Value: %x", x);
break;
}
z = sPC;
sPC = oldPC;
return z - (p->block + offs);
}
/*
* Given a reference to a C string array, its size,
* an object pointer, a function pointer (which must be
* within that object), an opcode NUMBER (important: this is not
* its offset!), disassemble lines of code starting from that opcode
* number into the given array. If the function ends before the array is
* filled, place a null pointer after the last position filled. Each
* created string is committed as a shared string; the number of
* lines written is also returned.
*
* Note: we use opcode numbers rather than offsets even though it's slower (you
* have to count the lines up to the appropriate offset). This is because it's
* eventually going to be accessible through an efun, and I want there to be
* no possibility of trying to disassemble stuff at an invalid offset (or
* beyond the end of the function - ugh).
* Later, there will be disass_object things so we can dump object globals,
* constants, etc (not that it's necessary now). There are security issues
* which should be dealt with in the efun header.
*/
int disass_func(char **arr, int asize, Obj * ob, Func * func, int first)
{
int x = 0;
char buff[20000];
/* sigh. Will be overwritten if the string is too long - fix this! */
int f = 0;
int offs = 0;
int ocnum = 0;
int arrcount = 0;
int y;
if (asize <= 0)
return 0;
if (!arr)
return 0;
/* function header, op code number 0 */
if (first < 1)
{
buff[0] = 0;
if (printable_type(buff, func->type))
strcat(buff, " ");
strcat(buff, func->name->str);
strcat(buff, "(");
for (x = 0; x < (int) func->num_arg; x++)
{
sprintf(buff + strlen(buff), "%sA.%d", (f ? ", " : ""), x);
f = 1;
}
strcat(buff, ")");
arrcount = 1;
arr[0] = strdup(buff);
}
ocnum = 1; /* just did the first line */
/*
* there are type checks before ENTER: a COPY n (which preserves the order of
* the args), and either a ADDSP or a TYPEC for each (which are probably in reverse order).
*/
x = func->num_arg;
f = 0;
while (!f)
{
if (asize <= arrcount)
return arrcount;
y = func->block[offs];
if (first > ocnum)
{ /* don't actually do it yet */
offs += printable_ocode(0, ob->code, func, offs);
if (y == S_ENTER)
f = 1;
if (y == S_END)
f = 2;
}
else
{
sprintf(buff, "%d:\t", offs);
offs += printable_ocode(buff + strlen(buff), ob->code, func, offs);
switch (y)
{
case S_COPY:
strcat(buff, "\t; Arg copy for type check");
break;
case S_ADDSP:
sprintf(buff + strlen(buff), "\t; (T_ANY) A.%d", --x);
break;
case S_TYPEC:
sprintf(buff + strlen(buff), "\t; A.%d", --x);
break;
case S_END:
strcat(buff, "\t; (premature function end)");
f = 2;
break;
case S_ENTER:
sprintf(buff + strlen(buff), "\t; Body: %s, %d locals",
func->name->str, func->block[offs - 1]);
f = 1;
break;
default:
strcat(buff, "\t; ???"); /* what'd this line doing beforeENTER? */
break;
}
arr[arrcount++] = strdup(buff);
}
ocnum++;
}
while ((offs < func->size) && (arrcount < asize))
{
if (first > ocnum)
{
offs += printable_ocode(0, ob->code, func, offs);
}
else
{
sprintf(buff, "%d:\t", offs);
offs += printable_ocode(buff + strlen(buff), ob->code, func, offs);
arr[arrcount++] = strdup(buff);
}
ocnum++;
}
if (arrcount < asize)
arr[arrcount] = 0;
return arrcount;
}
Val * lpc_disass(Val * obj, Val * fname, Val * opnum, Val * nmax)
{
Val *ret = NULL;
char *pc, **arr;
int arrayn;
int x;
Func *fhead;
if (nmax->u.number > MAX_ARRAY_SIZE)
{
efun_error("disassemble: array (%d) too big.", nmax->u.number);
return ret;
}
/* give them a read error if the object itself,
* or the inherited object, is non disass */
/* note that gods get disass privs even into /secure */
if ((Scurrent()->level < 10000) &&
!o_read_ok(Scurrent(), obj->u.ob->name))
{
efun_error("disassemble(%s): no read permission\n", obj->u.ob->name);
return ret;
}
fhead = find_program(fname->u.string, obj->u.ob, obj->u.ob, 0, 0);
if (!fhead)
{
return make_number(0);
}
arr = (char **) malloc(sizeof(char *) * (nmax->u.number + 1));
arrayn = disass_func(arr, nmax->u.number, obj->u.ob,
fhead, opnum->u.number);
ret = allocate_array(arrayn);
for (x = 0; x < arrayn; x++)
{
/* just move the string pointers(blecch :) */
ret->u.vec->item[x].type = T_STRING;
ret->u.vec->item[x].u.string = string_copy(arr[x]);
free(arr[x]);
}
free(arr);
return ret;
}
Shared * in_function_exists(Shared *fun, Class * ob)
{
Class *prog_ob;
Func *pr = 0;
int i;
#if 1
prog_ob = ob;
for (pr = prog_ob->prog; pr; pr = pr->next)
{
if (pr->name == fun)
return prog_ob->name;
}
/* not in main object - lets check the func table then! */
if (prog_ob->inherit)
for (i = 0; prog_ob->inherit[i]; i++)
{
pr = Hfunction(fun, prog_ob->inherit[i]->name);
if (pr)
return prog_ob->inherit[i]->name;
}
#endif
return NULL;
}
Val * lpc_function_exists(Val * fun, Val * ob)
{
Shared * oname = 0;
oname = in_function_exists(fun->u.string, ob->u.ob->code);
if (oname)
return share_string(oname);
else
return make_number(0);
}
Val * lpc_dump_status()
{
extern void dump_status_data();
dump_status_data();
return Const(0);
}
Val * lpc_dump_all_objects()
{
extern void dump_all_objects(int);
dump_all_objects(0);
return Const(0);
}
//
// Name: lpvm_registers
// Purpose: return an array of register names and current values
// Useful for internal debugging of the GD.
//
Val * lpvm_registers()
{
//extern int rSP, rBP, rCT, CBP, CATCHBP;
int num_reg = 5;
Val * ret;
ret = allocate_array(num_reg*2);
ret->u.vec->item[0].type = T_STRING;
ret->u.vec->item[0].u.string = string_copy("SP");
ret->u.vec->item[1].type = T_NUMBER;
ret->u.vec->item[1].u.number = rSP;
ret->u.vec->item[2].type = T_STRING;
ret->u.vec->item[2].u.string = string_copy("BP");
ret->u.vec->item[3].type = T_NUMBER;
ret->u.vec->item[3].u.number = rBP;
ret->u.vec->item[4].type = T_STRING;
ret->u.vec->item[4].u.string = string_copy("sPC");
ret->u.vec->item[5].type = T_NUMBER;
ret->u.vec->item[5].u.number = (uint32_t)((uint64_t)sPC & 0xffffffff);
ret->u.vec->item[6].type = T_STRING;
ret->u.vec->item[6].u.string = string_copy("CBP");
ret->u.vec->item[7].type = T_NUMBER;
ret->u.vec->item[7].u.number = CBP;
ret->u.vec->item[8].type = T_STRING;
ret->u.vec->item[8].u.string = string_copy("CATCHBP");
ret->u.vec->item[9].type = T_NUMBER;
ret->u.vec->item[9].u.number = CATCHBP;
return ret;
}
//
// Name: function call count
// Purpose: return each function/obj combination in the internal
// hash table and the count associated with each one
//
struct call_count
{
Shared * objname;
Shared * funcname;
int called;
};
typedef struct call_count CALLS;
int callcmp(CALLS ** p1, CALLS ** p2)
{
if ((*p1)->called == (*p2)->called) return 0;
if ((*p1)->called < (*p2)->called) return 1;
return -1;
}
#define TOP_CALLS 500
Val * lpc_function_count()
{
Val * ret;
CALLS **calls;
int i, count = 0, tot = 0;
// printf("lpc function count\n");
for (i = 0; i < IHSIZE; i++)
{
if (IHtable[i].used) tot++;
}
// printf("internal count=%d\n", tot);
calls = (CALLS **) malloc(tot * sizeof(CALLS *));
count = 0;
for (i = 0; (i < IHSIZE); i++)
{
if (IHtable[i].used)
{
calls[count] = (CALLS *)malloc(sizeof(CALLS));
calls[count]->objname = IHtable[i].obj;
calls[count]->funcname = IHtable[i].program->name;
calls[count]->called = IHtable[i].called;
count++;
}
}
// printf("call list built\n");
// Sort the calls
qsort(calls, tot, sizeof(CALLS *), callcmp);
// printf("call list sorted\n");
// Stick the top ones in an array for perusal
count = 0;
ret = allocate_array(TOP_CALLS * 3);
for (i = 0; (i < TOP_CALLS); i++)
{
ret->u.vec->item[count].type = T_STRING;
ret->u.vec->item[count++].u.string =
shared_string_copy(calls[i]->objname);
ret->u.vec->item[count].type = T_STRING;
ret->u.vec->item[count++].u.string =
shared_string_copy(calls[i]->funcname);
ret->u.vec->item[count].type = T_NUMBER;
ret->u.vec->item[count++].u.number = calls[i]->called;
}
// printf("return list created\n");
return ret;
}
// commands / sec
Val * lpc_command_load()
{
extern float LPC_load_av();
Val * ret = make_real(LPC_load_av());
return ret;
}
// compiled lines/ sec
Val * lpc_compile_load()
{
extern float LPC_compile_av();
Val * ret = make_real(LPC_compile_av());
return ret;
}
Val * lpc_creator(Val * ob)
{
if (ob->u.ob->su_name == 0)
return Const(0);
else
return share_string(ob->u.ob->su_name);
}