|
|
| 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++; |
| } | } |