--- freem/src/mdebug.c 2025/03/09 19:50:47 1.3 +++ freem/src/mdebug.c 2025/05/20 18:07:41 1.13 @@ -1,5 +1,5 @@ /* - * $Id: mdebug.c,v 1.3 2025/03/09 19:50:47 snw Exp $ + * $Id: mdebug.c,v 1.13 2025/05/20 18:07:41 snw Exp $ * debugger enhancements * * @@ -24,6 +24,36 @@ * along with FreeM. If not, see . * * $Log: mdebug.c,v $ + * 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 * @@ -35,12 +65,87 @@ #include #include #include +#include + +#include "config.h" + +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) +# include +# 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 +# elif defined(HAVE_HISTORY_H) +# include +# 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 "mdebug.h" #include "freem.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 */ short dbg_enable_watch; /* 0 = watches disabled, 1 = watches enabled */ int dbg_pending_watches; @@ -62,6 +167,293 @@ 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) { register int i; @@ -92,7 +484,7 @@ dbg_watch *dbg_add_watch (char *varnam) 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); fprintf (stderr, "Could not allocate memory for the new watchlist entry.\n"); set_io (MUMPS); @@ -104,7 +496,7 @@ dbg_watch *dbg_add_watch (char *varnam) dbg_watchlist[index].chgct = 0; 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); return NULL; @@ -116,9 +508,9 @@ void dbg_dump_watchlist (void) register int i; for (i = 0; i < MAXWATCH; i++) { - if (dbg_watchlist[i].firect) { - dbg_dump_watch (dbg_watchlist[i].varnam); - } + if (dbg_watchlist[i].firect) { + dbg_dump_watch (dbg_watchlist[i].varnam); + } } dbg_pending_watches = 0; @@ -229,14 +621,14 @@ char *dbg_get_watch_name (char *varnam) void dbg_fire_watch (char *varnam) { - dbg_watch *w; - - if ((w = dbg_find_watch (varnam)) == NULL) { - return; - } - - w->chgct++; - w->firect++; - dbg_pending_watches++; - + dbg_watch *w; + + if ((w = dbg_find_watch (varnam)) == NULL) { + return; + } + + w->chgct++; + w->firect++; + dbg_pending_watches++; + }