version 1.1, 2025/01/19 02:04:04
|
version 1.13, 2025/05/20 18:07:41
|
Line 1
|
Line 1
|
/* |
/* |
* * |
* $Id$ |
* * * |
|
* * * |
|
* *************** |
|
* * * * * |
|
* * MUMPS * |
|
* * * * * |
|
* *************** |
|
* * * |
|
* * * |
|
* * |
|
* |
|
* mdebug.c |
|
* debugger enhancements |
* debugger enhancements |
* |
* |
* |
* |
* Author: Serena Willis <jpw@coherent-logic.com> |
* Author: Serena Willis <snw@coherent-logic.com> |
* Copyright (C) 1998 MUG Deutschland |
* Copyright (C) 1998 MUG Deutschland |
* Copyright (C) 2020 Coherent Logic Development LLC |
* Copyright (C) 2020, 2025 Coherent Logic Development LLC |
* |
* |
* |
* |
* This file is part of FreeM. |
* This file is part of FreeM. |
Line 35
|
Line 23
|
* You should have received a copy of the GNU Affero Public License |
* You should have received a copy of the GNU Affero Public License |
* along with FreeM. If not, see <https://www.gnu.org/licenses/>. |
* along with FreeM. If not, see <https://www.gnu.org/licenses/>. |
* |
* |
|
* $Log$ |
|
* Revision 1.13 2025/05/20 18:07:41 snw |
|
* Add completion to debugger |
|
* |
|
* Revision 1.12 2025/05/01 21:02:31 snw |
|
* Documentation updates |
|
* |
|
* Revision 1.11 2025/05/01 17:02:30 snw |
|
* Further debugging improvements |
|
* |
|
* Revision 1.10 2025/05/01 04:45:15 snw |
|
* Improve backtraces |
|
* |
|
* Revision 1.9 2025/05/01 03:56:29 snw |
|
* -m |
|
* |
|
* Revision 1.8 2025/04/30 20:03:09 snw |
|
* Work on entryref parser |
|
* |
|
* Revision 1.7 2025/04/30 17:19:16 snw |
|
* Improve backtraces in debugger |
|
* |
|
* Revision 1.6 2025/04/30 14:41:03 snw |
|
* Further debugger work |
|
* |
|
* Revision 1.5 2025/04/29 18:46:17 snw |
|
* Begin work on interactive debugger |
|
* |
|
* Revision 1.4 2025/04/28 19:38:55 snw |
|
* Add trace mode |
|
* |
|
* Revision 1.3 2025/03/09 19:50:47 snw |
|
* Second phase of REUSE compliance and header reformat |
|
* |
|
* |
|
* SPDX-FileCopyrightText: (C) 2025 Coherent Logic Development LLC |
|
* SPDX-License-Identifier: AGPL-3.0-or-later |
**/ |
**/ |
|
|
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
|
#include <unistd.h> |
|
|
|
#include "config.h" |
|
|
|
#ifdef HAVE_LIBREADLINE |
|
# if defined(HAVE_READLINE_READLINE_H) |
|
# include <readline/readline.h> |
|
# elif defined(HAVE_READLINE_H) |
|
# include <readline.h> |
|
# else /* !defined(HAVE_READLINE_H) */ |
|
extern char *readline (); |
|
# endif /* !defined(HAVE_READLINE_H) */ |
|
/*char *cmdline = NULL;*/ |
|
#else /* !defined(HAVE_READLINE_READLINE_H) */ |
|
/* no readline */ |
|
#endif /* HAVE_LIBREADLINE */ |
|
|
|
#ifdef HAVE_READLINE_HISTORY |
|
# if defined(HAVE_READLINE_HISTORY_H) |
|
# include <readline/history.h> |
|
# elif defined(HAVE_HISTORY_H) |
|
# include <history.h> |
|
# else /* !defined(HAVE_HISTORY_H) */ |
|
extern void add_history (); |
|
extern int write_history (); |
|
extern int read_history (); |
|
# endif /* defined(HAVE_READLINE_HISTORY_H) */ |
|
/* no history */ |
|
#endif /* HAVE_READLINE_HISTORY */ |
|
|
|
|
#include "mpsdef.h" |
#include "mpsdef.h" |
#include "mdebug.h" |
#include "mdebug.h" |
#include "freem.h" |
#include "freem.h" |
#include "mref.h" |
#include "mref.h" |
|
|
|
#ifdef HAVE_LIBREADLINE |
|
char *dbg_commands[] = { |
|
"backtrace", |
|
"continue", |
|
"examine", |
|
"exit", |
|
"halt", |
|
"next", |
|
"quit", |
|
"step", |
|
"trace", |
|
"watch", |
|
NULL |
|
}; |
|
|
|
char **dbg_completion(const char *, int, int); |
|
char *dbg_generator(const char *, int); |
|
|
|
char **dbg_completion(const char *text, int start, int end) |
|
{ |
|
if (start > 0) return NULL; |
|
rl_attempted_completion_over = 1; |
|
return rl_completion_matches (text, dbg_generator); |
|
} |
|
|
|
char *dbg_generator(const char *text, int state) |
|
{ |
|
static int list_index, len; |
|
char *name; |
|
|
|
if (!state) { |
|
list_index = 0; |
|
len = strlen(text); |
|
} |
|
|
|
while ((name = dbg_commands[list_index++])) { |
|
if (strncmp (name, text, len) == 0) { |
|
return strdup (name); |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
#endif |
|
|
dbg_watch dbg_watchlist[MAXWATCH]; /* list of watchpoints */ |
dbg_watch dbg_watchlist[MAXWATCH]; /* list of watchpoints */ |
short dbg_enable_watch; /* 0 = watches disabled, 1 = watches enabled */ |
short dbg_enable_watch; /* 0 = watches disabled, 1 = watches enabled */ |
int dbg_pending_watches; |
int dbg_pending_watches; |
Line 67 void dbg_init (void)
|
Line 167 void dbg_init (void)
|
|
|
} |
} |
|
|
|
int debugger (int entry_mode, char *entryref) |
|
{ |
|
|
|
#if defined(HAVE_LIBREADLINE) |
|
static int first_entry = TRUE; |
|
static int stepmode = STEPMODE_NONE; |
|
|
|
char *dbg_buf; |
|
char dbg_prompt[256]; |
|
int dbg_argc; |
|
HIST_ENTRY **dbg_hist; |
|
int dbg_hist_idx; |
|
HIST_ENTRY *dbg_hist_ent; |
|
char rouname_buf[256]; |
|
char *savptr; |
|
char *dbg_cmd; |
|
char *dbarg; |
|
int do_prompt = FALSE; |
|
char cur_code[512]; |
|
char tbuf[512]; |
|
char tbuf2[512]; |
|
register int i; |
|
register int j; |
|
|
|
rl_attempted_completion_function = dbg_completion; |
|
set_io (UNIX); |
|
|
|
if (first_entry) { |
|
first_entry = FALSE; |
|
|
|
printf ("\nEntering debug mode; M commands unavailable.\n"); |
|
printf ("Type 'exit' to return to direct mode; 'help' for help.\n\n"); |
|
|
|
do_prompt = TRUE; |
|
} |
|
else { |
|
switch (entry_mode) { |
|
|
|
case DEBENTRY_CMD: |
|
if (stepmode == STEPMODE_CMD) { |
|
do_prompt = TRUE; |
|
} |
|
else { |
|
do_prompt = FALSE; |
|
} |
|
|
|
break; |
|
|
|
case DEBENTRY_LINE: |
|
if (stepmode == STEPMODE_CMD || stepmode == STEPMODE_LINE) { |
|
do_prompt = TRUE; |
|
} |
|
else { |
|
do_prompt = FALSE; |
|
} |
|
|
|
break; |
|
|
|
case DEBENTRY_BREAKPOINT: |
|
do_prompt = TRUE; |
|
break; |
|
|
|
case DEBENTRY_SIGINT: |
|
break; |
|
} |
|
} |
|
|
|
|
|
if (!do_prompt) return TRUE; |
|
|
|
while (1) { |
|
|
|
if (nstx == 0) { |
|
stcpy (rouname_buf, rou_name); |
|
} |
|
else { |
|
getraddress (tbuf, nstx); |
|
stcpy (rouname_buf, &(tbuf[3])); |
|
} |
|
stcnv_m2c (rouname_buf); |
|
|
|
if (stepmode != STEPMODE_NONE) { |
|
getraddress (tbuf, nstx); |
|
stcnv_m2c (tbuf); |
|
strcpy (tbuf2, &(tbuf[3])); |
|
routine_get_line (tbuf2, cur_code); |
|
|
|
printf ("%s\n", cur_code); |
|
} |
|
|
|
snprintf (dbg_prompt, sizeof (dbg_prompt) - 1, "%s $STACK=%d [DEBUG]> ", rouname_buf, nstx); |
|
if (dbg_enable_watch && dbg_pending_watches) dbg_dump_watchlist (); |
|
dbg_buf = readline (dbg_prompt); |
|
|
|
if (!dbg_buf) continue; |
|
if (strlen (dbg_buf) < 1) continue; |
|
|
|
dbg_argc = 1; |
|
for (i = 0; i < strlen (dbg_buf); i++) { |
|
if (dbg_buf[i] == ' ') dbg_argc++; |
|
} |
|
dbarg = dbg_buf; |
|
savptr = dbg_buf; |
|
dbg_cmd = strtok_r (dbg_buf, " ", &savptr); |
|
|
|
dbarg += strlen (dbg_cmd) + 1; |
|
|
|
if ((strcmp (dbg_cmd, "exit") == 0) || (strcmp (dbg_cmd, "quit") == 0)) { |
|
first_entry = TRUE; |
|
stepmode = STEPMODE_NONE; |
|
printf ("\n\nExiting debug mode.\n\n"); |
|
set_io (MUMPS); |
|
return FALSE; |
|
} |
|
else if ((strcmp (dbg_cmd, "examine") == 0) || (strcmp (dbg_cmd, "e") == 0)) { |
|
char *glvn; |
|
char key[STRLEN]; |
|
char res[STRLEN]; |
|
|
|
if (dbg_argc < 2) { |
|
printf ("debug: syntax error\n"); |
|
} |
|
else { |
|
glvn = dbarg; |
|
name_to_key (key, glvn, STRLEN - 1); |
|
if (key[0] != '^') { |
|
symtab (get_sym, key, res); |
|
} |
|
else { |
|
if (key[1] != '$') { |
|
global (get_sym, key, res); |
|
} |
|
else { |
|
ssvn (get_sym, key, res); |
|
} |
|
} |
|
|
|
if (merr () != OK) { |
|
if (merr () == M6) { |
|
printf ("examine: local variable %s is not defined\n", glvn); |
|
merr_clear (); |
|
} |
|
else if (merr () == M7) { |
|
printf ("examine: global variable %s is not defined\n", glvn); |
|
merr_clear (); |
|
} |
|
else { |
|
printf ("examine: error retrieving %s\n", glvn); |
|
merr_clear (); |
|
} |
|
} |
|
else { |
|
stcnv_m2c (res); |
|
printf ("%s=\"%s\"\n", glvn, res); |
|
} |
|
|
|
} |
|
} |
|
else if ((strcmp (dbg_cmd, "trace") == 0) || (strcmp (dbg_cmd, "t") == 0)) { |
|
if (trace_mode == 0) { |
|
trace_mode = 1; |
|
printf ("debug: trace on\n"); |
|
} |
|
else { |
|
trace_mode = 0; |
|
printf ("debug: trace off\n"); |
|
} |
|
ssvn_job_update (); |
|
} |
|
else if ((strcmp (dbg_cmd, "step") == 0) || (strcmp (dbg_cmd, "s") == 0)) { |
|
stepmode = STEPMODE_CMD; |
|
return TRUE; |
|
} |
|
else if ((strcmp (dbg_cmd, "next") == 0) || (strcmp (dbg_cmd, "n") == 0)) { |
|
stepmode = STEPMODE_LINE; |
|
return TRUE; |
|
} |
|
else if ((strcmp (dbg_cmd, "continue") == 0) || (strcmp (dbg_cmd, "cont") == 0) || (strcmp(dbg_cmd, "c") == 0)) { |
|
stepmode = STEPMODE_CONT; |
|
return TRUE; |
|
} |
|
else if ((strcmp (dbg_cmd, "backtrace") == 0) || (strcmp (dbg_cmd, "bt") == 0)) { |
|
char tmpbuf[1024]; |
|
char ecbuf[256]; |
|
char m_cmd[10]; |
|
char lref[1024]; |
|
char bt_mcode[1024]; |
|
|
|
printf ("%-10s%-10s%s\n", "$STACK", "COMMAND", "PLACE"); |
|
printf ("%-10s%-10s%s\n", "======", "=======", "====="); |
|
|
|
|
|
for (i = 1; i <= nstx; i++) getraddress (callerr[i], i); |
|
for (i = nstx; i > 0; i--) { |
|
|
|
stcpy (tmpbuf, callerr[i]); |
|
stcnv_m2c (tmpbuf); |
|
strcpy (ecbuf, &(tmpbuf[1])); |
|
|
|
for (j = 0; j < strlen (ecbuf); j++) { |
|
if (ecbuf[j] == ')') { |
|
ecbuf[j] = '\0'; |
|
break; |
|
} |
|
} |
|
|
|
switch (ecbuf[0]) { |
|
case 'F': |
|
sprintf (m_cmd, "FOR"); |
|
break; |
|
case 'D': |
|
sprintf (m_cmd, "DO"); |
|
break; |
|
} |
|
|
|
|
|
printf ("%-10d%-10s%s\n", i, m_cmd, &(tmpbuf[3])); |
|
stcpy (lref, &(tmpbuf[3])); |
|
stcnv_m2c (lref); |
|
if (routine_get_line (lref, bt_mcode) != NULL) { |
|
stcnv_m2c (bt_mcode); |
|
printf ("%-20s%s\n", " ", bt_mcode); |
|
} |
|
} |
|
stcpy (ecbuf, etrap); |
|
stcnv_m2c (ecbuf); |
|
printf ("ETRAP:\t%s\n", ecbuf); |
|
stcpy (ecbuf, ztrap[nstx]); |
|
stcnv_m2c (ecbuf); |
|
printf ("ZTRAP:\t%s\n", ecbuf); |
|
|
|
} |
|
else if ((strcmp (dbg_cmd, "halt") == 0) || (strcmp (dbg_cmd, "h") == 0)) { |
|
printf ("debug: forcing halt\n"); |
|
exit (0); |
|
} |
|
else if ((strcmp (dbg_cmd, "watch") == 0) || (strcmp (dbg_cmd, "w") == 0)) { |
|
char watchmode; |
|
char *glvn; |
|
|
|
if (dbg_argc == 1) { |
|
switch (dbg_enable_watch) { |
|
case 0: |
|
dbg_enable_watch = 1; |
|
printf ("debug: watchpoints enabled\n"); |
|
break; |
|
case 1: |
|
dbg_enable_watch = 0; |
|
printf ("debug: watchpoints disabled\n"); |
|
break; |
|
} |
|
} |
|
else { |
|
watchmode = dbarg[0]; |
|
glvn = &(dbarg[1]); |
|
|
|
switch (watchmode) { |
|
case '+': |
|
dbg_add_watch (glvn); |
|
break; |
|
case '-': |
|
dbg_remove_watch (glvn); |
|
break; |
|
case '?': |
|
dbg_dump_watch (glvn); |
|
break; |
|
default: |
|
printf ("debug: watch mode must be +, -, or ?\n"); |
|
break; |
|
} |
|
|
|
set_io (UNIX); |
|
} |
|
} |
|
else { |
|
printf ("DEBUG: invalid command\r\n"); |
|
} |
|
|
|
} |
|
|
|
return TRUE; |
|
#else |
|
return FALSE; |
|
#endif |
|
|
|
} |
|
|
dbg_watch *dbg_add_watch (char *varnam) |
dbg_watch *dbg_add_watch (char *varnam) |
{ |
{ |
register int i; |
register int i; |
Line 97 dbg_watch *dbg_add_watch (char *varnam)
|
Line 484 dbg_watch *dbg_add_watch (char *varnam)
|
return NULL; |
return NULL; |
} |
} |
|
|
if ((dbg_watchlist[index].varnam = (char *) malloc (256 * sizeof (char))) == NULL) { |
if ((dbg_watchlist[index].varnam = (char *) calloc (256, sizeof (char))) == NULL) { |
set_io (UNIX); |
set_io (UNIX); |
fprintf (stderr, "Could not allocate memory for the new watchlist entry.\n"); |
fprintf (stderr, "Could not allocate memory for the new watchlist entry.\n"); |
set_io (MUMPS); |
set_io (MUMPS); |
Line 109 dbg_watch *dbg_add_watch (char *varnam)
|
Line 496 dbg_watch *dbg_add_watch (char *varnam)
|
dbg_watchlist[index].chgct = 0; |
dbg_watchlist[index].chgct = 0; |
|
|
set_io (UNIX); |
set_io (UNIX); |
fprintf (stderr, "Added '%s' to the watchlist.\n", dbg_get_watch_name (varnam)); |
fprintf (stderr, "Added '%s' to the watchlist.\n", varnam); |
set_io (MUMPS); |
set_io (MUMPS); |
|
|
return NULL; |
return NULL; |
Line 121 void dbg_dump_watchlist (void)
|
Line 508 void dbg_dump_watchlist (void)
|
register int i; |
register int i; |
|
|
for (i = 0; i < MAXWATCH; i++) { |
for (i = 0; i < MAXWATCH; i++) { |
if (dbg_watchlist[i].firect) { |
if (dbg_watchlist[i].firect) { |
dbg_dump_watch (dbg_watchlist[i].varnam); |
dbg_dump_watch (dbg_watchlist[i].varnam); |
} |
} |
} |
} |
|
|
dbg_pending_watches = 0; |
dbg_pending_watches = 0; |
Line 234 char *dbg_get_watch_name (char *varnam)
|
Line 621 char *dbg_get_watch_name (char *varnam)
|
|
|
void dbg_fire_watch (char *varnam) { |
void dbg_fire_watch (char *varnam) { |
|
|
dbg_watch *w; |
dbg_watch *w; |
|
|
if ((w = dbg_find_watch (varnam)) == NULL) { |
if ((w = dbg_find_watch (varnam)) == NULL) { |
return; |
return; |
} |
} |
|
|
w->chgct++; |
w->chgct++; |
w->firect++; |
w->firect++; |
dbg_pending_watches++; |
dbg_pending_watches++; |
|
|
} |
} |