615ff675d2
[INTERPRETER] * BUG: Calling a jit function now correctly checks the number of arguments. * BUG: Fix a panic message.
623 lines
12 KiB
C
623 lines
12 KiB
C
/***************************************************************************
|
|
|
|
gbx_debug.c
|
|
|
|
(c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
***************************************************************************/
|
|
|
|
#define __GBX_DEBUG_C
|
|
|
|
#include "gb_common.h"
|
|
#include "gb_common_buffer.h"
|
|
#include "gb_common_case.h"
|
|
#include <stdarg.h>
|
|
|
|
#include "gb_buffer.h"
|
|
#include "gb_array.h"
|
|
#include "gb_error.h"
|
|
#include "gbx_exec.h"
|
|
#include "gbx_api.h"
|
|
#include "gbx_class.h"
|
|
#include "gbx_c_array.h"
|
|
#include "gbx_project.h"
|
|
#include "gbx_stack.h"
|
|
#include "gbx_subr.h"
|
|
#include "gbx_jit.h"
|
|
|
|
#include "gbx_debug.h"
|
|
|
|
DEBUG_INTERFACE DEBUG;
|
|
DEBUG_INFO *DEBUG_info = NULL;
|
|
int DEBUG_inside_eval = 0;
|
|
|
|
static bool calc_line_from_position(CLASS *class, FUNCTION *func, PCODE *addr, ushort *line)
|
|
{
|
|
int i;
|
|
ushort pos = addr - JIT_get_code(func);
|
|
ushort *post;
|
|
|
|
if (func->debug)
|
|
{
|
|
post = func->debug->pos;
|
|
for (i = 0; i < (func->debug->nline - 1); i++)
|
|
{
|
|
if (pos >= post[i] && pos < post[i + 1])
|
|
{
|
|
*line = i + func->debug->line;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*printf("pos = %d addr=%p func->code=%p\n", pos, addr, func->code);*/
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
const char *DEBUG_get_position(CLASS *cp, FUNCTION *fp, PCODE *pc)
|
|
{
|
|
#if DEBUG_MEMORY
|
|
static char buffer[256];
|
|
#endif
|
|
ushort line = 0;
|
|
|
|
if (!cp || !pc)
|
|
return "?";
|
|
|
|
if (fp != NULL && fp->debug)
|
|
calc_line_from_position(cp, fp, pc, &line);
|
|
|
|
#if DEBUG_MEMORY
|
|
snprintf(buffer, sizeof(buffer), "%s.%s.%d",
|
|
cp ? cp->name : "?",
|
|
(fp && fp->debug) ? fp->debug->name : "?",
|
|
line);
|
|
|
|
return buffer;
|
|
#else
|
|
snprintf(COMMON_buffer, COMMON_BUF_MAX, "%.64s.%.64s.%d",
|
|
cp->name,
|
|
(fp && fp->debug) ? fp->debug->name : "?",
|
|
line);
|
|
|
|
return COMMON_buffer;
|
|
#endif
|
|
}
|
|
|
|
|
|
const char *DEBUG_get_current_position(void)
|
|
{
|
|
return DEBUG_get_position(CP, FP, PC);
|
|
}
|
|
|
|
|
|
void DEBUG_init(void)
|
|
{
|
|
if (!EXEC_debug)
|
|
return;
|
|
|
|
COMPONENT_load(COMPONENT_create("gb.debug"));
|
|
LIBRARY_get_interface_by_name("gb.debug", DEBUG_INTERFACE_VERSION, &DEBUG);
|
|
|
|
DEBUG_info = DEBUG.Init((GB_DEBUG_INTERFACE *)(void *)GAMBAS_DebugApi, EXEC_fifo, EXEC_fifo_name);
|
|
|
|
if (!DEBUG_info)
|
|
ERROR_panic("Cannot initialize debug mode");
|
|
|
|
if (EXEC_profile)
|
|
{
|
|
EXEC_profile_instr = TRUE;
|
|
DEBUG.Profile.Init(EXEC_profile_path);
|
|
}
|
|
}
|
|
|
|
|
|
void DEBUG_exit(void)
|
|
{
|
|
if (!EXEC_debug || !DEBUG_is_init())
|
|
return;
|
|
|
|
DEBUG.Exit();
|
|
if (EXEC_profile)
|
|
DEBUG.Profile.Exit();
|
|
}
|
|
|
|
void DEBUG_enter_event_loop(void)
|
|
{
|
|
if (EXEC_profile)
|
|
DEBUG.Profile.Begin(NULL, NULL);
|
|
}
|
|
|
|
void DEBUG_leave_event_loop(void)
|
|
{
|
|
if (EXEC_profile)
|
|
DEBUG.Profile.End(NULL, NULL);
|
|
}
|
|
|
|
void DEBUG_where(void)
|
|
{
|
|
//static bool breakpoint = FALSE;
|
|
const char *where = DEBUG_get_current_position();
|
|
/*if (!breakpoint && !strcmp(where, "FForm._new.97"))
|
|
{
|
|
breakpoint = TRUE;
|
|
BREAKPOINT();
|
|
}*/
|
|
fprintf(stderr, "%s: ", where);
|
|
}
|
|
|
|
|
|
bool DEBUG_get_value(const char *sym, int len, GB_VARIANT *ret)
|
|
{
|
|
int i;
|
|
VALUE value;
|
|
LOCAL_SYMBOL *lp;
|
|
GLOBAL_SYMBOL *gp;
|
|
CLASS_VAR *var;
|
|
char *addr;
|
|
CLASS *class;
|
|
void *ref;
|
|
|
|
if (DEBUG_info->fp)
|
|
{
|
|
for (i = 0; i < DEBUG_info->fp->debug->n_local; i++)
|
|
{
|
|
lp = &DEBUG_info->fp->debug->local[i];
|
|
if (len == lp->sym.len && strncasecmp(sym, lp->sym.name, len) == 0)
|
|
{
|
|
if (lp->value >= 0)
|
|
value = DEBUG_info->bp[lp->value];
|
|
else
|
|
value = DEBUG_info->pp[lp->value];
|
|
goto __FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DEBUG_info->cp)
|
|
{
|
|
for (i = 0; i < DEBUG_info->cp->load->n_global; i++)
|
|
{
|
|
gp = &DEBUG_info->cp->load->global[i];
|
|
if (len != gp->sym.len || strncasecmp(sym, gp->sym.name, len) != 0)
|
|
continue;
|
|
|
|
if (CTYPE_get_kind(gp->ctype) == TK_VARIABLE)
|
|
{
|
|
if (!CTYPE_is_static(gp->ctype))
|
|
{
|
|
if (!DEBUG_info->op)
|
|
continue;
|
|
var = &DEBUG_info->cp->load->dyn[gp->value];
|
|
addr = (char *)DEBUG_info->op + var->pos;
|
|
ref = DEBUG_info->op;
|
|
}
|
|
else
|
|
{
|
|
var = &DEBUG_info->cp->load->stat[gp->value];
|
|
addr = (char *)DEBUG_info->cp->stat + var->pos;
|
|
ref = DEBUG_info->cp;
|
|
}
|
|
|
|
VALUE_class_read(DEBUG_info->cp, &value, addr, var->type, ref);
|
|
goto __FOUND;
|
|
}
|
|
else if (CTYPE_get_kind(gp->ctype) == TK_CONST)
|
|
{
|
|
VALUE_class_constant(DEBUG_info->cp, &value, gp->value);
|
|
goto __FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
//class = CLASS_look_global(sym, len);
|
|
class = CLASS_look(sym, len);
|
|
if (class)
|
|
{
|
|
if (class->auto_create && class->instance)
|
|
{
|
|
value._object.class = class;
|
|
value._object.object = class->instance;
|
|
value._object.super = NULL;
|
|
}
|
|
else
|
|
{
|
|
value.type = T_CLASS;
|
|
value._class.class = class;
|
|
value._class.super = NULL;
|
|
}
|
|
goto __FOUND;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
__FOUND:
|
|
|
|
BORROW(&value);
|
|
VALUE_conv_variant(&value);
|
|
UNBORROW(&value);
|
|
|
|
*((VALUE *)ret) = value;
|
|
return FALSE;
|
|
}
|
|
|
|
int DEBUG_set_value(const char *sym, int len, VALUE *value)
|
|
{
|
|
int i;
|
|
LOCAL_SYMBOL *lp;
|
|
GLOBAL_SYMBOL *gp;
|
|
CLASS_VAR *var;
|
|
char *addr;
|
|
VALUE *where;
|
|
bool ret = GB_DEBUG_SET_OK;
|
|
|
|
TRY
|
|
{
|
|
if (DEBUG_info->fp)
|
|
{
|
|
for (i = 0; i < DEBUG_info->fp->debug->n_local; i++)
|
|
{
|
|
lp = &DEBUG_info->fp->debug->local[i];
|
|
if (len == lp->sym.len && strncasecmp(sym, lp->sym.name, len) == 0)
|
|
{
|
|
if (lp->value >= 0)
|
|
where = &DEBUG_info->bp[lp->value];
|
|
else
|
|
where = &DEBUG_info->pp[lp->value];
|
|
VALUE_conv(value, where->type);
|
|
RELEASE(where);
|
|
*where = *value;
|
|
BORROW(where);
|
|
goto __FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DEBUG_info->cp)
|
|
{
|
|
for (i = 0; i < DEBUG_info->cp->load->n_global; i++)
|
|
{
|
|
gp = &DEBUG_info->cp->load->global[i];
|
|
if (len != gp->sym.len || strncasecmp(sym, gp->sym.name, len) != 0)
|
|
continue;
|
|
|
|
if (CTYPE_get_kind(gp->ctype) == TK_VARIABLE)
|
|
{
|
|
if (!CTYPE_is_static(gp->ctype) && DEBUG_info->op)
|
|
{
|
|
var = &DEBUG_info->cp->load->dyn[gp->value];
|
|
addr = (char *)DEBUG_info->op + var->pos;
|
|
}
|
|
else
|
|
{
|
|
var = &DEBUG_info->cp->load->stat[gp->value];
|
|
addr = (char *)DEBUG_info->cp->stat + var->pos;
|
|
}
|
|
|
|
VALUE_class_write(DEBUG_info->cp, value, addr, var->type);
|
|
goto __FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = GB_DEBUG_SET_READ_ONLY;
|
|
|
|
__FOUND:
|
|
|
|
0;
|
|
}
|
|
CATCH
|
|
{
|
|
ret = GB_DEBUG_SET_ERROR;
|
|
}
|
|
END_TRY
|
|
|
|
if (ret == GB_DEBUG_SET_ERROR)
|
|
EXEC_set_native_error(TRUE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DEBUG_get_object_access_type(void *object, CLASS *class, int *count)
|
|
{
|
|
CLASS_DESC *desc;
|
|
CLASS_DESC_METHOD *dm;
|
|
char type;
|
|
int access = GB_DEBUG_ACCESS_NORMAL;
|
|
|
|
//fprintf(stderr, "DEBUG_can_be_used_like_an_array: %p %s ?\n", object, class->name);
|
|
|
|
if (!object)
|
|
goto __NORMAL;
|
|
|
|
if (class == CLASS_Class || OBJECT_is_class(object))
|
|
{
|
|
class = (CLASS *)object;
|
|
object = NULL;
|
|
CLASS_load(class);
|
|
}
|
|
|
|
dm = CLASS_get_special_desc(class, SPEC_GET);
|
|
if (!dm)
|
|
{
|
|
//fprintf(stderr, "No _get method\n");
|
|
goto __NORMAL;
|
|
}
|
|
|
|
if (dm->npmin != 1 || dm->npmax != 1)
|
|
{
|
|
//fprintf(stderr, "No _get(Arg AS Integer) method\n");
|
|
goto __NORMAL;
|
|
}
|
|
|
|
if (*dm->signature == T_INTEGER)
|
|
access = GB_DEBUG_ACCESS_ARRAY;
|
|
else if (*dm->signature == T_STRING)
|
|
access = GB_DEBUG_ACCESS_COLLECTION;
|
|
else
|
|
goto __NORMAL;
|
|
|
|
desc = CLASS_get_symbol_desc(class, "Count");
|
|
if (!desc)
|
|
goto __NORMAL;
|
|
|
|
type = CLASS_DESC_get_type(desc);
|
|
|
|
// The two only possible cases:
|
|
// A static read-only property, and object == NULL
|
|
// or a dynamic read-only property, and object != NULL
|
|
|
|
if (!((type == CD_PROPERTY_READ && object) || (type == CD_STATIC_PROPERTY_READ && !object)))
|
|
goto __NORMAL;
|
|
|
|
TRY
|
|
{
|
|
if (desc->property.native)
|
|
{
|
|
if (EXEC_call_native(desc->property.read, object, desc->property.type, 0))
|
|
access = GB_DEBUG_ACCESS_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
EXEC.class = desc->property.class;
|
|
EXEC.object = object;
|
|
EXEC.nparam = 0;
|
|
EXEC.native = FALSE;
|
|
EXEC.index = (int)(intptr_t)desc->property.read;
|
|
//EXEC.func = &class->load->func[(int)desc->property.read];
|
|
|
|
EXEC_function_keep();
|
|
|
|
TEMP = *RP;
|
|
UNBORROW(RP);
|
|
RP->type = T_VOID;
|
|
}
|
|
|
|
if (access != GB_DEBUG_ACCESS_NORMAL)
|
|
{
|
|
VALUE_conv_integer(&TEMP);
|
|
*count = TEMP._integer.value;
|
|
}
|
|
}
|
|
CATCH
|
|
{
|
|
access = GB_DEBUG_ACCESS_NORMAL;
|
|
}
|
|
END_TRY
|
|
|
|
|
|
// For collection-like objects, check _next and Key property
|
|
|
|
if (access == GB_DEBUG_ACCESS_COLLECTION)
|
|
{
|
|
dm = CLASS_get_special_desc(class, SPEC_NEXT);
|
|
if (!dm)
|
|
goto __NORMAL;
|
|
|
|
desc = CLASS_get_symbol_desc(class, "Key");
|
|
if (!desc)
|
|
goto __NORMAL;
|
|
|
|
type = CLASS_DESC_get_type(desc);
|
|
if (type != CD_PROPERTY_READ && type != CD_PROPERTY && type != CD_STATIC_PROPERTY_READ && type != CD_STATIC_PROPERTY && type != CD_VARIABLE && type != CD_STATIC_VARIABLE)
|
|
goto __NORMAL;
|
|
}
|
|
|
|
return access;
|
|
|
|
__NORMAL:
|
|
return GB_DEBUG_ACCESS_NORMAL;
|
|
}
|
|
|
|
void DEBUG_print_backtrace(STACK_BACKTRACE *bt)
|
|
{
|
|
int i, n;
|
|
bool stop;
|
|
STACK_BACKTRACE *NO_WARNING(end);
|
|
//STACK_CONTEXT *sc = (STACK_CONTEXT *)(STACK_base + STACK_size) - err->bt_count;
|
|
|
|
//fprintf(stderr, "0: %s\n", DEBUG_get_position(err->cp, err->fp, err->pc));
|
|
for (i = 0, n = 0, stop = FALSE; !stop; i++)
|
|
{
|
|
//fprintf(stderr, "%d: %s\n", i, DEBUG_get_position(bt[i].cp, bt[i].fp, bt[i].pc));
|
|
if (STACK_backtrace_is_end(&bt[i]))
|
|
{
|
|
stop = TRUE;
|
|
end = &bt[i];
|
|
STACK_backtrace_clear_end(end);
|
|
}
|
|
if (bt[i].pc)
|
|
{
|
|
n++;
|
|
fprintf(stderr, "%s ", DEBUG_get_position(bt[i].cp, bt[i].fp, bt[i].pc));
|
|
}
|
|
}
|
|
fputc('\n', stderr);
|
|
|
|
STACK_backtrace_set_end(end);
|
|
}
|
|
|
|
|
|
void DEBUG_print_current_backtrace(void)
|
|
{
|
|
STACK_BACKTRACE *bt = STACK_get_backtrace();
|
|
if (bt)
|
|
{
|
|
DEBUG_print_backtrace(bt);
|
|
STACK_free_backtrace(&bt);
|
|
}
|
|
}
|
|
|
|
|
|
GB_ARRAY DEBUG_get_string_array_from_backtrace(STACK_BACKTRACE *bt)
|
|
{
|
|
GB_ARRAY array;
|
|
int i, n, size;
|
|
STACK_BACKTRACE *end;
|
|
|
|
for (i = 0, size = 0;; i++)
|
|
{
|
|
if (bt[i].pc)
|
|
size++;
|
|
if (STACK_backtrace_is_end(&bt[i]))
|
|
{
|
|
end = &bt[i];
|
|
STACK_backtrace_clear_end(end);
|
|
break;
|
|
}
|
|
}
|
|
|
|
GB_ArrayNew(&array, GB_T_STRING, size);
|
|
//*((char **)GB_ArrayGet(array, 0)) = STRING_new_zero(DEBUG_get_position(err->cp, err->fp, err->pc));
|
|
|
|
for (i = 0, n = 0; n < size; i++)
|
|
{
|
|
if (!bt[i].pc)
|
|
continue;
|
|
*((char **)GB_ArrayGet(array, n)) = STRING_new_zero(DEBUG_get_position(bt[i].cp, bt[i].fp, bt[i].pc));
|
|
n++;
|
|
}
|
|
|
|
STACK_backtrace_set_end(end);
|
|
return array;
|
|
}
|
|
|
|
GB_CLASS DEBUG_find_class(const char *name)
|
|
{
|
|
CLASS *class;
|
|
CLASS *save = CP;
|
|
|
|
// As the startup class is automatically exported, this is the only way for the debugger to find it.
|
|
if (PROJECT_class && !strcmp(name, PROJECT_class->name))
|
|
return (GB_CLASS)PROJECT_class;
|
|
|
|
CP = NULL;
|
|
class = CLASS_find(name);
|
|
CP = save;
|
|
|
|
return (GB_CLASS)class;
|
|
}
|
|
|
|
void DEBUG_enum_keys(void *object, GB_DEBUG_ENUM_CB cb)
|
|
{
|
|
CENUM *old;
|
|
CENUM *cenum = NULL;
|
|
CLASS *class;
|
|
bool err = FALSE;
|
|
char *str;
|
|
int len;
|
|
char *save_key;
|
|
int save_lkey;
|
|
GB_VALUE value;
|
|
void *prop_ob;
|
|
|
|
old = EXEC_enum;
|
|
|
|
class = OBJECT_class(object);
|
|
if (class == CLASS_Class)
|
|
{
|
|
class = (CLASS *)object;
|
|
object = NULL;
|
|
}
|
|
|
|
prop_ob = object ? object : class;
|
|
|
|
TRY
|
|
{
|
|
GB_GetProperty(prop_ob, "Key");
|
|
SUBR_get_string_len(&TEMP, &save_key, &save_lkey);
|
|
(*cb)(save_key, save_lkey);
|
|
|
|
cenum = CENUM_create(prop_ob);
|
|
|
|
EXEC_enum = cenum;
|
|
EXEC_special(SPEC_FIRST, class, object, 0, TRUE);
|
|
EXEC_enum = old;
|
|
|
|
if (cenum->stop)
|
|
{
|
|
err = TRUE;
|
|
goto __END;
|
|
}
|
|
|
|
for(;;)
|
|
{
|
|
EXEC_enum = cenum;
|
|
err = EXEC_special(SPEC_NEXT, class, object, 0, TRUE);
|
|
EXEC_enum = old;
|
|
|
|
if (err || cenum->stop)
|
|
{
|
|
err = TRUE;
|
|
break;
|
|
}
|
|
|
|
GB_GetProperty(prop_ob, "Key");
|
|
|
|
SUBR_get_string_len(&TEMP, &str, &len);
|
|
(*cb)(str, len);
|
|
}
|
|
|
|
__END:
|
|
|
|
value.type = GB_T_CSTRING;
|
|
value._string.value.addr = save_key;
|
|
value._string.value.start = 0;
|
|
value._string.value.len = save_lkey;
|
|
GB_SetProperty(prop_ob, "Key", &value);
|
|
|
|
if (err)
|
|
OBJECT_UNREF(cenum);
|
|
}
|
|
CATCH
|
|
{
|
|
OBJECT_UNREF(cenum);
|
|
}
|
|
END_TRY
|
|
}
|
|
|
|
void DEBUG_enter_eval()
|
|
{
|
|
DEBUG_inside_eval++;
|
|
}
|
|
|
|
void DEBUG_leave_eval()
|
|
{
|
|
DEBUG_inside_eval--;
|
|
}
|