gambas-source-code/main/lib/debug/debug.c
Benoît Minisini b936c83f0d [CONFIGURATION]
* NEW: Update copyright strings.

[DEVELOPMENT ENVIRONMENT]
* NEW: Display menu shortcuts inside the form editor.


git-svn-id: svn://localhost/gambas/trunk@3670 867c0c6c-44f3-4631-809d-bfa615b0a4ec
2011-03-21 00:04:10 +00:00

1020 lines
19 KiB
C

/***************************************************************************
debug.c
(c) 2000-2011 Benoît Minisini <gambas@users.sourceforge.net>
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., 675 Mass Ave, Cambridge, MA 02139, USA.
***************************************************************************/
#define __DEBUG_C
// Do not include gbx_debug.h
#define __GBX_DEBUG_H
#include "gb_common.h"
#include "gambas.h"
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include "gb_error.h"
#include "gbx_type.h"
#include "gb_limit.h"
#include "gbx_stack.h"
#include "gbx_class.h"
#include "gbx_exec.h"
#include "gbx_local.h"
#include "gbx_object.h"
#include "gbx_eval.h"
#include "print.h"
#include "debug.h"
//#define DEBUG_ME
DEBUG_INFO DEBUG_info = { 0 };
GB_DEBUG_INTERFACE *DEBUG_interface;
char DEBUG_buffer[DEBUG_BUFFER_MAX + 1];
static DEBUG_BREAK *Breakpoint;
static bool Error;
static EVAL_INTERFACE EVAL;
static int _fdr;
static int _fdw;
static FILE *_out;
static FILE *_in;
static bool _fifo;
#define EXEC_current (*(STACK_CONTEXT *)GB_DEBUG.GetExec())
#ifdef DEBUG_ME
#define WARNING(_msg, ...) fprintf(stderr, "W\t" _msg "\n", ##__VA_ARGS__)
#define INFO(_msg, ...) fprintf(stderr, "I\t" _msg "\n", ##__VA_ARGS__)
#else
#define WARNING(_msg, ...) fprintf(_out, "W\t" _msg "\n", ##__VA_ARGS__)
#define INFO(_msg, ...) fprintf(_out, "I\t" _msg "\n", ##__VA_ARGS__)
#endif
static void init_eval_interface()
{
static bool init = FALSE;
if (!init)
{
GB.GetInterface("gb.eval", EVAL_INTERFACE_VERSION, &EVAL);
init = TRUE;
}
}
void DEBUG_break_on_next_line(void)
{
DEBUG_info.stop = TRUE;
DEBUG_info.leave = FALSE;
DEBUG_info.fp = NULL;
DEBUG_info.bp = NULL;
}
static void signal_user(int sig)
{
signal(SIGUSR1, signal_user);
#ifdef DEBUG_ME
fprintf(stderr, "Got SIGUSR1\n");
#endif
/*CAPP_got_signal();*/
DEBUG_break_on_next_line();
}
static boolean calc_line_from_position(CLASS *class, FUNCTION *func, PCODE *addr, ushort *line)
{
int i;
ushort pos = addr - func->code;
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;
}
static boolean calc_position_from_line(CLASS *class, ushort line, FUNCTION **function, PCODE **addr)
{
int i;
ushort pos, pos_after;
FUNCTION *func = NULL;
FUNC_DEBUG *debug = NULL;
for (i = 0; i < class->load->n_func; i++)
{
func = &class->load->func[i];
debug = func->debug;
if (debug && line >= debug->line && line < (debug->line + debug->nline))
break;
}
if (i >= class->load->n_func)
return TRUE;
line -= debug->line;
for(;;)
{
pos = debug->pos[line];
pos_after = debug->pos[line + 1];
if (pos != pos_after)
break;
line++;
if (line >= debug->nline)
return TRUE;
}
*function = func;
*addr = &func->code[pos];
/*printf("%s.%d -> %04X\n", class->name, line + debug->line, **addr);*/
return FALSE;
}
DEBUG_INFO *DEBUG_init(GB_DEBUG_INTERFACE *debug, bool fifo, const char *fifo_name)
{
char path[DEBUG_FIFO_PATH_MAX];
char name[16];
//int i;
//if (!EXEC_debug)
// return;
DEBUG_interface = debug;
_fifo = fifo;
if (_fifo)
{
if (!fifo_name)
{
sprintf(name, "%d", getppid());
fifo_name = name;
}
snprintf(path, sizeof(path), "/tmp/gambas.%d/%s.out", getuid(), fifo_name);
/*for (i = 0; i < 20; i++)
{
_fdr = open(path, O_RDONLY | O_NONBLOCK);
if (_fdr >= 0)
break;
usleep(10000);
}
if (_fdr < 0)
return NULL;*/
_fdr = open(path, O_RDONLY);
if (_fdr < 0)
return NULL;
fcntl(_fdr, F_SETFD, FD_CLOEXEC);
snprintf(path, sizeof(path), "/tmp/gambas.%d/%s.in", getuid(), fifo_name);
_fdw = open(path, O_WRONLY);
if (_fdw < 0)
return NULL;
fcntl(_fdw, F_SETFD, FD_CLOEXEC);
_in = fdopen(_fdr, "r");
_out = fdopen(_fdw, "w");
if (!_in || !_out)
return NULL;
//ERROR_panic("Cannot open fifos");
setlinebuf(_in);
//setvbuf(_in, NULL, _IONBF, 0);
setlinebuf(_out);
//setvbuf(_out, NULL, _IONBF, 0);
}
else
{
_in = stdin;
_out = stdout;
}
//ARRAY_create(&Breakpoint);
GB.NewArray(&Breakpoint, sizeof(DEBUG_BREAK), 16);
signal(SIGUSR1, signal_user);
signal(SIGPIPE, SIG_IGN);
setlinebuf(_out);
return &DEBUG_info;
}
void DEBUG_exit(void)
{
GB.FreeArray(&Breakpoint);
/* Don't do it, it blocks!
if (EXEC_fifo)
{
fclose(_in);
fclose(_out);
}
*/
}
static int find_free_breakpoint(void)
{
int i;
char used[MAX_BREAKPOINT];
memset(used, FALSE, MAX_BREAKPOINT);
for (i = 0; i < ARRAY_count(Breakpoint); i++)
used[Breakpoint[i].id - 1] = TRUE;
for (i = 0; i < MAX_BREAKPOINT; i++)
if (!used[i])
return (i + 1);
return 0;
}
static bool init_breakpoint(DEBUG_BREAK *brk)
{
PCODE *addr = NULL;
FUNCTION *func;
//fprintf(stderr, "init_breakpoint: id = %d\n", brk->id);
if (brk->addr || brk->class->state == CS_NULL)
{
WARNING("Breakpoint is pending");
return TRUE;
}
if (CLASS_is_native(brk->class) || !brk->class->debug)
{
WARNING("Cannot set breakpoint: no debugging information");
return TRUE;
}
if (calc_position_from_line(brk->class, brk->line, &func, &addr))
{
WARNING("Cannot set breakpoint: cannot calculate position");
//fprintf(_out, "Cannot calc position from line number\n");
return TRUE;
}
if (!PCODE_is_breakpoint(*addr))
{
WARNING("Cannot set breakpoint: Not a line beginning");
//fprintf(_out, "Not a line beginning ?\n");
return TRUE;
}
if (*addr & 0xFF)
{
WARNING("Breakpoint already set");
//fprintf(_out, "Breakpoint already set\n");
return FALSE;
}
brk->addr = addr;
*addr = PCODE_BREAKPOINT(brk->id);
//fprintf(stderr, "init_breakpoint: OK\n");
#ifdef DEBUG_ME
fprintf(stderr, "init_breakpoint: %s.%d\n", brk->class->name, brk->line);
#endif
INFO("Breakpoint set: %s.%d", brk->class->name, brk->line);
return FALSE;
}
static boolean set_breakpoint(CLASS *class, ushort line)
{
DEBUG_BREAK *brk;
int id;
if (GB.Count(Breakpoint) >= MAX_BREAKPOINT)
{
WARNING("Too many breakpoints");
return TRUE;
}
id = find_free_breakpoint();
if (id == 0)
{
WARNING("Cannot create breakpoint");
return TRUE;
}
brk = (DEBUG_BREAK *)GB.Add(&Breakpoint);
brk->id = id;
brk->addr = NULL;
brk->class = class;
brk->line = line;
#ifdef DEBUG_ME
fprintf(stderr, "set_breakpoint: %s.%d\n", class->name, line);
#endif
init_breakpoint(brk);
return FALSE;
}
static boolean unset_breakpoint(CLASS *class, ushort line)
{
int i;
DEBUG_BREAK *brk;
for (i = 0; i < GB.Count(Breakpoint); i++)
{
brk = &Breakpoint[i];
if (brk->class == class && brk->line == line)
{
if (brk->addr)
*(brk->addr) = PCODE_BREAKPOINT(0);
GB.Remove(&Breakpoint, i, 1);
#ifdef DEBUG_ME
fprintf(stderr, "unset_breakpoint: %s.%d\n", class->name, line);
#endif
INFO("Breakpoint removed");
return FALSE;
}
}
WARNING("Unknown breakpoint");
return TRUE;
}
void DEBUG_init_breakpoints(CLASS *class)
{
int i;
DEBUG_BREAK *brk;
#ifdef DEBUG_ME
fprintf(stderr, "DEBUG_init_breakpoints: %p %s\n", class, class->name);
#endif
for (i = 0; i < GB.Count(Breakpoint); i++)
{
brk = &Breakpoint[i];
if (brk->class == class)
{
//fprintf(stderr, "DEBUG_init_breakpoints: %s\n", class->name);
init_breakpoint(brk);
}
}
}
static void print_local()
{
int i;
FUNCTION *fp;
LOCAL_SYMBOL *lp;
fp = DEBUG_info.fp;
if (!fp || !fp->debug)
return;
for (i = 0; i < fp->debug->n_local; i++)
{
lp = &fp->debug->local[i];
fprintf(_out, "%.*s ", lp->sym.len, lp->sym.name);
}
/*else
{
cmd++;
for (i = 0; i < FP->debug->n_local; i++)
{
lp = &FP->debug->local[i];
if (lp->sym.len == strlen(cmd) && strncasecmp(lp->sym.name, cmd, lp->sym.len) == 0)
{
fprintf(_out, "=");
//fprintf(_out, "TUT\n");
PRINT_value(_out, &BP[lp->value], TRUE);
nl = FALSE;
break;
}
}
}*/
}
static void print_symbol(GLOBAL_SYMBOL *gp, bool is_static, bool is_public)
{
if (CTYPE_get_kind(gp->ctype) != TK_VARIABLE && CTYPE_get_kind(gp->ctype) != TK_CONST)
return;
if (CTYPE_is_static(gp->ctype) && !is_static)
return;
if (!CTYPE_is_static(gp->ctype) && is_static)
return;
if (CTYPE_is_public(gp->ctype) && !is_public)
return;
if (!CTYPE_is_public(gp->ctype) && is_public)
return;
fprintf(_out, "%.*s ", gp->sym.len, gp->sym.name);
}
static void print_object()
{
int i;
GLOBAL_SYMBOL *gp;
CLASS *cp = DEBUG_info.cp;
void *op = DEBUG_info.op;
if (!cp || !cp->load)
return;
fprintf(_out, "S: ");
for (i = 0; i < cp->load->n_global; i++)
{
gp = &cp->load->global[i];
print_symbol(gp, TRUE, TRUE);
}
fprintf(_out, "s: ");
for (i = 0; i < cp->load->n_global; i++)
{
gp = &cp->load->global[i];
print_symbol(gp, TRUE, FALSE);
}
if (op)
{
fprintf(_out, "D: ");
for (i = 0; i < cp->load->n_global; i++)
{
gp = &cp->load->global[i];
print_symbol(gp, FALSE, TRUE);
}
fprintf(_out, "d: ");
for (i = 0; i < cp->load->n_global; i++)
{
gp = &cp->load->global[i];
print_symbol(gp, FALSE, FALSE);
}
}
}
static void command_quit(const char *cmd)
{
exit(1);
}
static void command_go(const char *cmd)
{
GB.Signal(GB_SIGNAL_DEBUG_CONTINUE, 0);
DEBUG_info.stop = FALSE;
DEBUG_info.leave = FALSE;
DEBUG_info.fp = NULL;
DEBUG_info.bp = NULL;
}
static void command_step(const char *cmd)
{
GB.Signal(GB_SIGNAL_DEBUG_FORWARD, 0);
DEBUG_break_on_next_line();
}
static void command_next(const char *cmd)
{
GB.Signal(GB_SIGNAL_DEBUG_FORWARD, 0);
DEBUG_info.stop = TRUE;
DEBUG_info.leave = FALSE;
DEBUG_info.fp = FP;
DEBUG_info.bp = BP;
}
static void command_from(const char *cmd)
{
STACK_CONTEXT *sc = GB_DEBUG.GetStack(0); //STACK_get_current();
if (sc && sc->pc)
{
GB.Signal(GB_SIGNAL_DEBUG_FORWARD, 0);
DEBUG_info.stop = TRUE;
DEBUG_info.leave = FALSE;
DEBUG_info.fp = sc->fp;
DEBUG_info.bp = sc->bp;
}
else
{
GB.Signal(GB_SIGNAL_DEBUG_FORWARD, 0);
DEBUG_info.stop = TRUE;
DEBUG_info.leave = TRUE;
DEBUG_info.fp = FP;
DEBUG_info.bp = BP;
}
}
static void command_set_breakpoint(const char *cmd)
{
char class_name[64];
ushort line;
//CLASS *class;
if (sscanf(cmd, "+%64[^.].%hu", class_name, &line) != 2)
WARNING("Cannot set breakpoint: syntax error");
else
{
//class = (CLASS *)GB.FindClassLocal(class_name);
//CLASS_load_without_init(class);
//fprintf(stderr, "command_set_breakpoint: %s %s\n", class->name, class->component ? class->component->name : "?");
set_breakpoint((CLASS *)GB_DEBUG.FindClass(class_name), line);
}
}
static void command_unset_breakpoint(const char *cmd)
{
char class_name[64];
ushort line;
if (sscanf(cmd, "-%64[^.].%hu", class_name, &line) != 2)
WARNING("Cannot remove breakpoint: Syntax error");
else
{
//class = CLASS_find(class_name);
//CLASS_load_without_init(class);
unset_breakpoint((CLASS *)GB_DEBUG.FindClass(class_name), line);
}
}
void DEBUG_backtrace(FILE *out)
{
int i, n;
STACK_CONTEXT *context;
ushort line;
if (CP)
fprintf(out, "%s", DEBUG_get_current_position());
else
fprintf(out, "?");
//for (i = 0; i < (STACK_frame_count - 1); i++)
n = 0;
for (i = 0;; i++)
{
context = GB_DEBUG.GetStack(i); //&STACK_frame[i];
if (!context)
break;
if (context->pc)
{
line = 0;
if (calc_line_from_position(context->cp, context->fp, context->pc, &line))
n += fprintf(out, " %s.?.?", context->cp->name);
else
n += fprintf(out, " %s.%s.%d", context->cp->name, context->fp->debug->name, line);
}
else if (context->cp)
n += fprintf(out, " ?");
if (n >= (DEBUG_OUTPUT_MAX_SIZE / 2))
{
fprintf(out, " ...");
break;
}
}
}
static void debug_info()
{
fprintf(_out, "*\t");
if (Error)
GB_DEBUG.PrintError(_out, TRUE, FALSE);
fprintf(_out, "\t");
DEBUG_backtrace(_out);
fprintf(_out, "\t");
print_local();
fprintf(_out, "\t");
print_object();
fprintf(_out, "\n");
}
static void command_frame(const char *cmd)
{
int i;
int frame;
STACK_CONTEXT *context = NULL;
if (cmd)
{
frame = atoi(&cmd[1]);
//fprintf(_out, "switching to frame %d\n", frame);
if (frame > 0)
{
for (i = 0;; i++)
{
context = GB_DEBUG.GetStack(i);
if (!context)
break;
if (!context->pc && !context->cp)
continue;
frame--;
if (!frame)
{
DEBUG_info.bp = context->bp;
DEBUG_info.fp = context->fp;
DEBUG_info.op = context->op;
DEBUG_info.cp = context->cp;
break;
}
}
}
}
if (!context)
{
DEBUG_info.bp = BP;
DEBUG_info.fp = FP;
DEBUG_info.op = OP;
DEBUG_info.cp = CP;
}
debug_info();
}
static void command_eval(const char *cmd)
{
EXPRESSION *expr;
ERROR_INFO save_error = { 0 };
DEBUG_INFO save_debug;
VALUE *val;
int start, len;
FILE *out;
const char *name;
int ret;
init_eval_interface();
out = *cmd == '!' ? stdout : _out;
len = strlen(cmd);
for (start = 0; start < len; start++)
{
if (cmd[start] == '\t')
break;
if (*cmd != '!')
fputc(cmd[start], _out);
}
if (start >= len)
return;
if (*cmd != '!')
fprintf(_out, "\t");
GB_DEBUG.SaveError(&save_error);
save_debug = DEBUG_info;
start++;
EVAL.New(POINTER(&expr), &cmd[start], len - start);
if (EVAL.Compile(expr, *cmd == '='))
{
if (*cmd != '!')
fprintf(_out, "!");
fputs(expr->error, out);
goto __END;
}
val = (VALUE *)EVAL.Run(expr, GB_DEBUG.GetValue);
if (!val)
goto __ERROR;
switch(*cmd)
{
case '?':
PRINT_value(out, val, TRUE);
break;
case '!':
PRINT_value(out, val, FALSE);
break;
case '#':
PRINT_object(out, val);
break;
case '=':
if (!EVAL.GetAssignmentSymbol(expr, &name, &len))
{
ret = GB_DEBUG.SetValue(name, len, val);
if (ret == GB_DEBUG_SET_ERROR)
goto __ERROR;
else if (ret == GB_DEBUG_SET_READ_ONLY)
{
fprintf(out, "!%.*s is read-only", len, name);
goto __END;
}
}
fprintf(out, "OK");
break;
}
goto __END;
__ERROR:
if (*cmd != '!')
fprintf(out, "!");
GB_DEBUG.PrintError(out, TRUE, FALSE);
__END:
EVAL.Free(POINTER(&expr));
DEBUG_info = save_debug; //.cp = NULL;
GB_DEBUG.RestoreError(&save_error);
fprintf(out, "\n");
fflush(out);
}
static void command_symbol(const char *cmd)
{
int start, len;
DEBUG_INFO save_debug = DEBUG_info;
len = strlen(cmd);
for (start = 0; start < len; start++)
{
if (cmd[start] == '\t')
break;
fputc(cmd[start], _out);
}
if (start >= len)
return;
fprintf(_out, "\t");
/*DEBUG_info.bp = BP;
DEBUG_info.fp = FP;
DEBUG_info.op = OP;
DEBUG_info.cp = CP;*/
start++;
PRINT_symbol(_out, &cmd[start], len - start);
fprintf(_out, "\n");
fflush(_out);
DEBUG_info = save_debug;
}
void DEBUG_main(boolean error)
{
static DEBUG_TYPE last_command = TC_NONE;
static DEBUG_COMMAND Command[] =
{
{ "q", TC_NONE, command_quit, FALSE },
{ "n", TC_NEXT, command_next, FALSE },
{ "s", TC_STEP, command_step, FALSE },
{ "f", TC_FROM, command_from, FALSE },
{ "g", TC_GO, command_go, FALSE },
{ "+", TC_NONE, command_set_breakpoint, TRUE },
{ "-", TC_NONE, command_unset_breakpoint, TRUE },
//{ "w", TC_NONE, command_where, TRUE },
//{ "l", TC_NONE, command_local, TRUE },
{ "&", TC_NONE, command_symbol, TRUE },
{ "?", TC_NONE, command_eval, TRUE },
{ "!", TC_NONE, command_eval, TRUE },
{ "#", TC_NONE, command_eval, TRUE },
{ "=", TC_NONE, command_eval, TRUE },
//{ "e", TC_NONE, command_error, TRUE },
{ "@", TC_NONE, command_frame, TRUE },
{ NULL }
};
char *cmd = NULL;
char cmdbuf[64];
int len;
DEBUG_COMMAND *tc = NULL;
/*static int cpt = 0;*/
Error = error;
fflush(NULL);
#ifdef DEBUG_ME
fprintf(stderr, "DEBUG_main {\n");
#endif
if (_fifo)
fprintf(_out, "!\n");
command_frame(NULL);
do
{
/*if (CP == NULL)
printf("[]:");
else
printf("[%s%s]:", DEBUG_get_current_position(), Error ? "*" : "");*/
GB.Signal(GB_SIGNAL_DEBUG_BREAK, 0);
if (!_fifo)
{
fprintf(_out, "> ");
fflush(NULL);
}
GB.FreeString(&cmd);
for(;;)
{
*cmdbuf = 0;
errno = 0;
if (fgets(cmdbuf, sizeof(cmdbuf), _in) == NULL && errno != EINTR)
break;
if (!*cmdbuf)
continue;
GB.AddString(&cmd, cmdbuf, 0);
if (cmd[GB.StringLength(cmd) - 1] == '\n')
break;
}
len = GB.StringLength(cmd);
// A null string command means an I/O error
if (len == 0)
{
fprintf(stderr, "warning: debugger I/O error: %s\n", strerror(errno));
exit(1);
}
if (len > 0 && cmd[len - 1] == '\n')
{
len--;
cmd[len] = 0;
}
#ifdef DEBUG_ME
fprintf(stderr, "--> %s\n", cmd);
#endif
if (len == 0)
{
if (last_command == TC_NONE)
continue;
for (tc = Command; tc->pattern; tc++)
{
if (tc->type == last_command)
{
(*tc->func)(cmd);
break;
}
}
}
else
{
for (tc = Command; tc->pattern; tc++)
{
if (strncasecmp(tc->pattern, cmd, strlen(tc->pattern)) == 0)
{
if (tc->type != TC_NONE)
last_command = tc->type;
(*tc->func)(cmd);
break;
}
}
}
if (tc->pattern == NULL)
WARNING("Unknown command: %s", cmd);
fflush(_out);
}
while (last_command == TC_NONE || tc->pattern == NULL || tc->loop);
GB.FreeString(&cmd);
#ifdef DEBUG_ME
fprintf(stderr, "} DEBUG_main\n");
#endif
}
void DEBUG_breakpoint(int id)
{
DEBUG_main(FALSE);
}
const char *DEBUG_get_position(CLASS *cp, FUNCTION *fp, PCODE *pc)
{
ushort line = 0;
if (fp != NULL && fp->debug)
calc_line_from_position(cp, fp, pc, &line);
snprintf(DEBUG_buffer, sizeof(DEBUG_buffer), "%.64s.%.64s.%d",
cp ? cp->name : "?",
(fp && fp->debug) ? fp->debug->name : "?",
line);
return DEBUG_buffer;
}
const char *DEBUG_get_current_position(void)
{
return DEBUG_get_position(CP, FP, PC);
}
void DEBUG_where(void)
{
fprintf(_out ? _out : stderr, "%s: ", DEBUG_get_current_position());
}
void DEBUG_welcome(void)
{
if (!_fifo)
fprintf(_out, DEBUG_WELCOME);
}