/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */
/*
 * Purpose: Basic debugging support.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "erl_process.h"
#include "error.h"
#include "erl_driver.h"
#include "bif.h"
#include "big.h"
#include "external.h"
#include "beam_load.h"
#include "beam_bp.h"
#include "erl_binary.h"

#ifdef ARCH_64
# define HEXF "%016bpX"
#else
# define HEXF "%08bpX"
#endif

void dbg_bt(Process* p, Eterm* sp);
void dbg_where(Eterm* addr, Eterm x0, Eterm* reg);

static void print_big(int to, void *to_arg, Eterm* addr);
static int print_op(int to, void *to_arg, int op, int size, Eterm* addr);
Eterm
erts_debug_same_2(Process* p, Eterm term1, Eterm term2)
{
    return (term1 == term2) ? am_true : am_false;
}

Eterm
erts_debug_flat_size_1(Process* p, Eterm term)
{
    Uint size = size_object(term);

    if (IS_USMALL(0, size)) {
	BIF_RET(make_small(size));
    } else {
	Eterm* hp = HAlloc(p, BIG_UINT_HEAP_SIZE);
	BIF_RET(uint_to_big(size, hp));
    }
}

Eterm
erts_debug_breakpoint_2(Process* p, Eterm MFA, Eterm bool)
{
    Eterm* tp;
    Eterm mfa[3];
    int i;
    int specified = 0;
    Eterm res;

    if (bool != am_true && bool != am_false)
	goto error;

    if (is_not_tuple(MFA)) {
	goto error;
    }
    tp = tuple_val(MFA);
    if (*tp != make_arityval(3)) {
	goto error;
    }
    mfa[0] = tp[1];
    mfa[1] = tp[2];
    mfa[2] = tp[3];
    if (!is_atom(mfa[0]) || !is_atom(mfa[1]) ||
	(!is_small(mfa[2]) && mfa[2] != am_Underscore)) {
	goto error;
    }
    for (i = 0; i < 3 && mfa[i] != am_Underscore; i++, specified++) {
	/* Empty loop body */
    }
    for (i = specified; i < 3; i++) {
	if (mfa[i] != am_Underscore) {
	    goto error;
	}
    }
    if (is_small(mfa[2])) {
	mfa[2] = signed_val(mfa[2]);
    }

    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
    erts_smp_block_system(0);

    if (bool == am_true) {
	res = make_small(erts_set_debug_break(mfa, specified));
    } else {
	res = make_small(erts_clear_debug_break(mfa, specified));
    }

    erts_smp_release_system();
    erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN);

    return res;

 error:
    BIF_ERROR(p, BADARG);
}

Eterm
erts_debug_disassemble_1(Process* p, Eterm addr)
{
    erts_dsprintf_buf_t *dsbufp;
    Eterm* hp;
    Eterm* tp;
    Eterm bin;
    Eterm mfa;
    Eterm* funcinfo = NULL;	/* Initialized to eliminate warning. */
    Uint* code_base;
    Uint* code_ptr = NULL;	/* Initialized to eliminate warning. */
    Uint instr;
    Uint uaddr;
    Uint hsz;
    int i;

    if (term_to_Uint(addr, &uaddr)) {
	code_ptr = (Uint *) uaddr;
	if ((funcinfo = find_function_from_pc(code_ptr)) == NULL) {
	    BIF_RET(am_false);
	}
    } else if (is_tuple(addr)) {
	Module* modp;
	Eterm mod;
	Eterm name;
	Export* ep;
	Sint arity;
	int n;

	tp = tuple_val(addr);
	if (tp[0] != make_arityval(3)) {
	error:
	    BIF_ERROR(p, BADARG);
	}
	mod = tp[1];
	name = tp[2];
	if (!is_atom(mod) || !is_atom(name) || !is_small(tp[3])) {
	    goto error;
	}
	arity = signed_val(tp[3]);
	modp = erts_get_module(mod);

	/*
	 * Try the export entry first to allow disassembly of special functions
	 * such as erts_debug:apply/4.  Then search for it in the module.
	 */

	if ((ep = erts_find_function(mod, name, arity)) != NULL) {
	    /* XXX: add "&& ep->address != ep->code+3" condition?
	     * Consider a traced function.
	     * Its ep will have ep->address == ep->code+3.
	     * erts_find_function() will return the non-NULL ep.
	     * Below we'll try to derive a code_ptr from ep->address.
	     * But this code_ptr will point to the start of the Export,
	     * not the function's func_info instruction. BOOM !?
	     */
	    code_ptr = ((Eterm *) ep->address) - 5;
	    funcinfo = code_ptr+2;
	} else if (modp == NULL || (code_base = modp->code) == NULL) {
	    BIF_RET(am_undef);
	} else {
	    n = code_base[MI_NUM_FUNCTIONS];
	    for (i = 0; i < n; i++) {
		code_ptr = (Uint *) code_base[MI_FUNCTIONS+i];
		if (code_ptr[3] == name && code_ptr[4] == arity) {
		    funcinfo = code_ptr+2;
		    break;
		}
	    }
	    if (i == n) {
		BIF_RET(am_undef);
	    }
	}
    } else {
	goto error;
    }

    dsbufp = erts_create_tmp_dsbuf(0);
    erts_print(ERTS_PRINT_DSBUF, (void *) dsbufp, HEXF ": ", code_ptr);
    instr = (Uint) code_ptr[0];
    for (i = 0; i < NUM_SPECIFIC_OPS; i++) {
	if (instr == (Uint) BeamOp(i) && opc[i].name[0] != '\0') {
	    code_ptr += print_op(ERTS_PRINT_DSBUF, (void *) dsbufp,
				 i, opc[i].sz-1, code_ptr+1) + 1;
	    break;
	}
    }
    if (i >= NUM_SPECIFIC_OPS) {
	erts_print(ERTS_PRINT_DSBUF, (void *) dsbufp,
		   "unknown " HEXF "\n", instr);
	code_ptr++;
    }
    bin = new_binary(p, (byte *) dsbufp->str, (int) dsbufp->str_len);
    erts_destroy_tmp_dsbuf(dsbufp);
    hsz = 4+4;
    (void) erts_bld_uint(NULL, &hsz, (Uint) code_ptr);
    hp = HAlloc(p, hsz);
    addr = erts_bld_uint(&hp, NULL, (Uint) code_ptr);
    ASSERT(is_atom(funcinfo[0]));
    ASSERT(is_atom(funcinfo[1]));
    mfa = TUPLE3(hp, funcinfo[0], funcinfo[1], make_small(funcinfo[2]));
    hp += 4;
    return TUPLE3(hp, addr, bin, mfa);
}

void
dbg_bt(Process* p, Eterm* sp)
{
    Eterm* stack = STACK_START(p);

    while (sp < stack) {
	if (is_CP(*sp)) {
	    Eterm* addr = find_function_from_pc(cp_val(*sp));
	    if (addr)
		erts_fprintf(stderr,
			     HEXF ": %T:%T/%bpu\n",
			     addr, addr[0], addr[1], addr[2]);
	}
	sp++;
    }
}

void
dbg_where(Eterm* addr, Eterm x0, Eterm* reg)
{
    Eterm* f = find_function_from_pc(addr);

    if (f == NULL) {
	erts_fprintf(stderr, "???\n");
    } else {
	int arity;
	int i;

	addr = f;
	arity = addr[2];
	erts_fprintf(stderr, HEXF ": %T:%T(", addr, addr[0], addr[1]);
	for (i = 0; i < arity; i++)
	    erts_fprintf(stderr, i ? ", %T" : "%T", i ? reg[i] : x0);
	erts_fprintf(stderr, ")\n");
    }
}

static int
print_op(int to, void *to_arg, int op, int size, Eterm* addr)
{
    int i;
    Uint tag;
    char* sign;
    char* start_prog;		/* Start of program for packer. */
    char* prog;			/* Current position in packer program. */
    Uint stack[8];		/* Stack for packer. */
    Uint* sp = stack;		/* Points to next free position. */
    Uint packed = 0;		/* Accumulator for packed operations. */
    Uint args[8];		/* Arguments for this instruction. */
    Uint* ap;			/* Pointer to arguments. */

    /*
     * Copy all arguments to a local buffer.
     */

    ASSERT(size <= sizeof(args)/sizeof(args[0]));
    ap = args;
    for (i = 0; i < size; i++) {
	*ap++ = addr[i];
    }

    /*
     * Undo any packing done by the loader.  This is easily done by running
     * the packing program backwards and in reverse.
     */

    start_prog = opc[op].pack;
    prog = start_prog + strlen(start_prog);
    while (start_prog < prog) {
	prog--;
	switch (*prog) {
	case 'g':
	    *ap++ = *--sp;
	    break;
	case 'i':	/* Initialize packing accumulator. */
	    *ap++ = packed;
	    break;
	case 's':
	    *ap++ = packed & 0x3ff;
	    packed >>= 10;
	    break;
	case '0':	/* Tight shift */
	    *ap++ = packed & (BEAM_TIGHT_MASK / sizeof(Eterm));
	    packed >>= BEAM_TIGHT_SHIFT;
	    break;
	case '6':	/* Shift 16 steps */
	    *ap++ = packed & 0xffff;
	    packed >>= 16;
	    break;
	case 'p':
	    *sp++ = *--ap;
	    break;
	case 'P':
	    packed = *--sp;
	    break;
	default:
	    ASSERT(0);
	}
    }
    
    /*
     * Print the name and all operands of the instructions.
     */
	
    erts_print(to, to_arg, "%s ", opc[op].name);
    ap = args;
    sign = opc[op].sign;
    while (*sign) {
	switch (*sign) {
	case 'r':		/* x(0) */
	    erts_print(to, to_arg, "x(0)");
	    break;
	case 'x':		/* x(N) */
	    erts_print(to, to_arg, "x(%d)", reg_index(ap[0]));
	    ap++;
	    break;
	case 'y':		/* y(N) */
	    erts_print(to, to_arg, "y(%d)", reg_index(ap[0]) - CP_SIZE);
	    ap++;
	    break;
	case 'n':		/* Nil */
	    erts_print(to, to_arg, "[]");
	    break;
	case 's':		/* Any source (tagged constant or register) */
	    tag = beam_reg_tag(*ap);
	    if (tag == X_REG_DEF) {
		erts_print(to, to_arg, "x(%d)", reg_index(*ap));
		ap++;
		break;
	    } else if (tag == Y_REG_DEF) {
		erts_print(to, to_arg, "y(%d)", reg_index(*ap) - CP_SIZE);
		ap++;
		break;
	    } else if (tag == R_REG_DEF) {
		erts_print(to, to_arg, "x(0)");
		ap++;
		break;
	    }
	    /*FALLTHROUGH*/
	case 'a':		/* Tagged atom */
	case 'i':		/* Tagged integer */
	case 'c':		/* Tagged constant */
	    erts_print(to, to_arg, "%T", *ap);
	    ap++;
	    break;
	case 'A':
	    erts_print(to, to_arg, "%d", arityval(ap[0]));
	    ap++;
	    break;
	case 'd':		/* Destination (x(0), x(N), y(N)) */
	    switch (beam_reg_tag(*ap)) {
	    case X_REG_DEF:
		erts_print(to, to_arg, "x(%d)", reg_index(*ap));
		break;
	    case Y_REG_DEF:
		erts_print(to, to_arg, "y(%d)", reg_index(*ap) - CP_SIZE);
		break;
	    case R_REG_DEF:
		erts_print(to, to_arg, "x(0)");
		break;
	    }
	    ap++;
	    break;
	case 'I':		/* Untagged integer. */
	case 't':
	    erts_print(to, to_arg, "%d", *ap);
	    ap++;
	    break;
	case 'f':		/* Destination label */
	    erts_print(to, to_arg, "f(%X)", *ap);
	    ap++;
	    break;
	case 'p':		/* Pointer (to label) */
	    {
		Eterm* f = find_function_from_pc((Eterm *)*ap);

		if (f+3 != (Eterm *) *ap) {
		    erts_print(to, to_arg, "p(%X)", *ap);
		} else {
		    erts_print(to, to_arg, "%T:%T/%bpu", f[0], f[1], f[2]);
		}
		ap++;
	    }
	    break;
	case 'j':		/* Pointer (to label) */
	    erts_print(to, to_arg, "j(%X)", *ap);
	    ap++;
	    break;
	case 'e':		/* Export entry */
	    {
		Export* ex = (Export *) *ap;
		erts_print(to, to_arg,
			   "%T:%T/%bpu", ex->code[0], ex->code[1], ex->code[2]);
		ap++;
	    }
	    break;
	case 'F':		/* Function definition */
	    break;
	case 'b':
	    for (i = 0; i < BIF_SIZE; i++) {
		BifFunction bif = (BifFunction) *ap;
		if (bif == bif_table[i].f) {
		    break;
		}
	    }
	    if (i == BIF_SIZE) {
		erts_print(to, to_arg, "b(%d)", (Uint) *ap);
	    } else {
		Eterm name = bif_table[i].name;
		unsigned arity = bif_table[i].arity;
		erts_print(to, to_arg, "%T/%u", name, arity);
	    }
	    ap++;
	    break;
	case 'P':	/* Byte offset into tuple (see beam_load.c) */
	    erts_print(to, to_arg, "%d", (*ap / sizeof(Eterm*)) - 1);
	    ap++;
	    break;
	case 'w':
	    {
		int arity = thing_arityval(ap[0]);
		print_big(to, to_arg, ap);
		ap += arity + 1;
		size += arity;
	    }
	    break;
	case 'o':
	    {
		FloatDef f;
#ifdef ARCH_64
		ap++;
		f.fdw = *ap++;
#else
		ap++;
		f.fw[0] = *ap++;
		f.fw[1] = *ap++;
#endif
		erts_print(to, to_arg, "%g", f.fd);
		size++;
	    }
	    break;
	case 'l':		/* fr(N) */
	    erts_print(to, to_arg, "fr(%d)", reg_index(ap[0]));
	    ap++;
	    break;
	default:
	    erts_print(to, to_arg, "???");
	    ap++;
	    break;
	}
	erts_print(to, to_arg, " ");
	sign++;
    }

    /*
     * Print more information about certain instructions.
     */

    ap = addr + size;
    switch (op) {
    case op_i_select_val_sfI:
	{
	    int n = ap[-1];

	    while (n > 0) {
		erts_print(to, to_arg, "%T f(%X) ", ap[0], ap[1]);
		ap += 2;
		size += 2;
		n--;
	    }
	}
	break;
    case op_i_jump_on_val_sfII:
	{
	    int n;
	    for (n = ap[-2]; n > 0; n--) {
		erts_print(to, to_arg, "f(%X) ", ap[0]);
		ap++;
		size++;
	    }
	}
	break;
    case op_i_select_big_sf:
	while (ap[0]) {
	    int arity = thing_arityval(ap[0]);
	    print_big(to, to_arg, ap);
	    size += arity+1;
	    ap += arity+1;
	    erts_print(to, to_arg, " f(%X) ", ap[0]);
	    ap++;
	    size++;
	}
	ap++;
	size++;
	break;
    }
    erts_print(to, to_arg, "\n");

    return size;
}

static void
print_big(int to, void *to_arg, Eterm* addr)
{
    int i;
    int k;

    i = BIG_SIZE(addr);
    if (BIG_SIGN(addr))
	erts_print(to, to_arg, "-#integer(%d) = {", i);
    else
	erts_print(to, to_arg, "#integer(%d) = {", i);
    erts_print(to, to_arg, "%d", BIG_DIGIT(addr, 0));
    for (k = 1; k < i; k++)
	erts_print(to, to_arg, ",%d", BIG_DIGIT(addr, k));
    erts_print(to, to_arg, "}");
}
