Annotation of freem/src/mdebug.c, revision 1.13
1.1 snw 1: /*
1.13 ! snw 2: * $Id: mdebug.c,v 1.12 2025/05/01 21:02:31 snw Exp $
1.1 snw 3: * debugger enhancements
4: *
5: *
1.2 snw 6: * Author: Serena Willis <snw@coherent-logic.com>
1.1 snw 7: * Copyright (C) 1998 MUG Deutschland
1.3 snw 8: * Copyright (C) 2020, 2025 Coherent Logic Development LLC
1.1 snw 9: *
10: *
11: * This file is part of FreeM.
12: *
13: * FreeM is free software: you can redistribute it and/or modify
14: * it under the terms of the GNU Affero Public License as published by
15: * the Free Software Foundation, either version 3 of the License, or
16: * (at your option) any later version.
17: *
18: * FreeM is distributed in the hope that it will be useful,
19: * but WITHOUT ANY WARRANTY; without even the implied warranty of
20: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21: * GNU Affero Public License for more details.
22: *
23: * You should have received a copy of the GNU Affero Public License
24: * along with FreeM. If not, see <https://www.gnu.org/licenses/>.
25: *
1.4 snw 26: * $Log: mdebug.c,v $
1.13 ! snw 27: * Revision 1.12 2025/05/01 21:02:31 snw
! 28: * Documentation updates
! 29: *
1.12 snw 30: * Revision 1.11 2025/05/01 17:02:30 snw
31: * Further debugging improvements
32: *
1.11 snw 33: * Revision 1.10 2025/05/01 04:45:15 snw
34: * Improve backtraces
35: *
1.10 snw 36: * Revision 1.9 2025/05/01 03:56:29 snw
37: * -m
38: *
1.9 snw 39: * Revision 1.8 2025/04/30 20:03:09 snw
40: * Work on entryref parser
41: *
1.8 snw 42: * Revision 1.7 2025/04/30 17:19:16 snw
43: * Improve backtraces in debugger
44: *
1.7 snw 45: * Revision 1.6 2025/04/30 14:41:03 snw
46: * Further debugger work
47: *
1.6 snw 48: * Revision 1.5 2025/04/29 18:46:17 snw
49: * Begin work on interactive debugger
50: *
1.5 snw 51: * Revision 1.4 2025/04/28 19:38:55 snw
52: * Add trace mode
53: *
1.4 snw 54: * Revision 1.3 2025/03/09 19:50:47 snw
55: * Second phase of REUSE compliance and header reformat
56: *
1.3 snw 57: *
58: * SPDX-FileCopyrightText: (C) 2025 Coherent Logic Development LLC
59: * SPDX-License-Identifier: AGPL-3.0-or-later
1.1 snw 60: **/
61:
62: #include <stdio.h>
63: #include <stdlib.h>
64: #include <string.h>
1.5 snw 65: #include <unistd.h>
66:
67: #include "config.h"
68:
69: #ifdef HAVE_LIBREADLINE
70: # if defined(HAVE_READLINE_READLINE_H)
71: # include <readline/readline.h>
72: # elif defined(HAVE_READLINE_H)
73: # include <readline.h>
74: # else /* !defined(HAVE_READLINE_H) */
75: extern char *readline ();
76: # endif /* !defined(HAVE_READLINE_H) */
77: /*char *cmdline = NULL;*/
78: #else /* !defined(HAVE_READLINE_READLINE_H) */
79: /* no readline */
80: #endif /* HAVE_LIBREADLINE */
81:
82: #ifdef HAVE_READLINE_HISTORY
83: # if defined(HAVE_READLINE_HISTORY_H)
84: # include <readline/history.h>
85: # elif defined(HAVE_HISTORY_H)
86: # include <history.h>
87: # else /* !defined(HAVE_HISTORY_H) */
88: extern void add_history ();
89: extern int write_history ();
90: extern int read_history ();
91: # endif /* defined(HAVE_READLINE_HISTORY_H) */
92: /* no history */
93: #endif /* HAVE_READLINE_HISTORY */
94:
1.1 snw 95:
96: #include "mpsdef.h"
97: #include "mdebug.h"
98: #include "freem.h"
99: #include "mref.h"
100:
1.13 ! snw 101: #ifdef HAVE_LIBREADLINE
! 102: char *dbg_commands[] = {
! 103: "backtrace",
! 104: "continue",
! 105: "examine",
! 106: "exit",
! 107: "halt",
! 108: "next",
! 109: "quit",
! 110: "step",
! 111: "trace",
! 112: "watch",
! 113: NULL
! 114: };
! 115:
! 116: char **dbg_completion(const char *, int, int);
! 117: char *dbg_generator(const char *, int);
! 118:
! 119: char **dbg_completion(const char *text, int start, int end)
! 120: {
! 121: if (start > 0) return NULL;
! 122: rl_attempted_completion_over = 1;
! 123: return rl_completion_matches (text, dbg_generator);
! 124: }
! 125:
! 126: char *dbg_generator(const char *text, int state)
! 127: {
! 128: static int list_index, len;
! 129: char *name;
! 130:
! 131: if (!state) {
! 132: list_index = 0;
! 133: len = strlen(text);
! 134: }
! 135:
! 136: while ((name = dbg_commands[list_index++])) {
! 137: if (strncmp (name, text, len) == 0) {
! 138: return strdup (name);
! 139: }
! 140: }
! 141:
! 142: return NULL;
! 143: }
! 144: #endif
! 145:
1.1 snw 146: dbg_watch dbg_watchlist[MAXWATCH]; /* list of watchpoints */
147: short dbg_enable_watch; /* 0 = watches disabled, 1 = watches enabled */
148: int dbg_pending_watches;
149:
150:
151: void dbg_init (void)
152: {
153: register int i;
154:
155: dbg_enable_watch = 0;
156: dbg_pending_watches = 0;
157:
158: for (i = 0; i < MAXWATCH; i++) {
159:
160: dbg_watchlist[i].varnam = NULL;
161: dbg_watchlist[i].chgct = 0;
162:
163: }
164:
165: }
166:
1.11 snw 167: int debugger (int entry_mode, char *entryref)
1.5 snw 168: {
169:
170: #if defined(HAVE_LIBREADLINE)
171: static int first_entry = TRUE;
1.6 snw 172: static int stepmode = STEPMODE_NONE;
173:
1.5 snw 174: char *dbg_buf;
175: char dbg_prompt[256];
1.11 snw 176: int dbg_argc;
1.5 snw 177: HIST_ENTRY **dbg_hist;
178: int dbg_hist_idx;
179: HIST_ENTRY *dbg_hist_ent;
180: char rouname_buf[256];
1.6 snw 181: char *savptr;
182: char *dbg_cmd;
1.11 snw 183: char *dbarg;
1.6 snw 184: int do_prompt = FALSE;
1.11 snw 185: char cur_code[512];
186: char tbuf[512];
187: char tbuf2[512];
1.6 snw 188: register int i;
1.10 snw 189: register int j;
1.13 ! snw 190:
! 191: rl_attempted_completion_function = dbg_completion;
1.5 snw 192: set_io (UNIX);
193:
194: if (first_entry) {
195: first_entry = FALSE;
196:
197: printf ("\nEntering debug mode; M commands unavailable.\n");
198: printf ("Type 'exit' to return to direct mode; 'help' for help.\n\n");
1.6 snw 199:
200: do_prompt = TRUE;
1.5 snw 201: }
1.6 snw 202: else {
203: switch (entry_mode) {
204:
205: case DEBENTRY_CMD:
206: if (stepmode == STEPMODE_CMD) {
207: do_prompt = TRUE;
208: }
209: else {
210: do_prompt = FALSE;
211: }
212:
213: break;
214:
215: case DEBENTRY_LINE:
216: if (stepmode == STEPMODE_CMD || stepmode == STEPMODE_LINE) {
217: do_prompt = TRUE;
218: }
219: else {
220: do_prompt = FALSE;
221: }
222:
223: break;
224:
225: case DEBENTRY_BREAKPOINT:
226: do_prompt = TRUE;
227: break;
228:
229: case DEBENTRY_SIGINT:
230: break;
231: }
232: }
233:
234:
235: if (!do_prompt) return TRUE;
1.5 snw 236:
237: while (1) {
1.6 snw 238:
1.7 snw 239: if (nstx == 0) {
240: stcpy (rouname_buf, rou_name);
241: }
242: else {
243: getraddress (tbuf, nstx);
244: stcpy (rouname_buf, &(tbuf[3]));
245: }
1.5 snw 246: stcnv_m2c (rouname_buf);
247:
1.11 snw 248: if (stepmode != STEPMODE_NONE) {
249: getraddress (tbuf, nstx);
250: stcnv_m2c (tbuf);
251: strcpy (tbuf2, &(tbuf[3]));
252: routine_get_line (tbuf2, cur_code);
253:
254: printf ("%s\n", cur_code);
255: }
256:
1.5 snw 257: snprintf (dbg_prompt, sizeof (dbg_prompt) - 1, "%s $STACK=%d [DEBUG]> ", rouname_buf, nstx);
1.12 snw 258: if (dbg_enable_watch && dbg_pending_watches) dbg_dump_watchlist ();
1.5 snw 259: dbg_buf = readline (dbg_prompt);
1.11 snw 260:
1.5 snw 261: if (!dbg_buf) continue;
1.11 snw 262: if (strlen (dbg_buf) < 1) continue;
1.5 snw 263:
1.11 snw 264: dbg_argc = 1;
265: for (i = 0; i < strlen (dbg_buf); i++) {
266: if (dbg_buf[i] == ' ') dbg_argc++;
267: }
268: dbarg = dbg_buf;
1.6 snw 269: savptr = dbg_buf;
270: dbg_cmd = strtok_r (dbg_buf, " ", &savptr);
1.11 snw 271:
272: dbarg += strlen (dbg_cmd) + 1;
273:
1.6 snw 274: if ((strcmp (dbg_cmd, "exit") == 0) || (strcmp (dbg_cmd, "quit") == 0)) {
1.5 snw 275: first_entry = TRUE;
1.6 snw 276: stepmode = STEPMODE_NONE;
1.5 snw 277: printf ("\n\nExiting debug mode.\n\n");
278: set_io (MUMPS);
279: return FALSE;
280: }
1.11 snw 281: else if ((strcmp (dbg_cmd, "examine") == 0) || (strcmp (dbg_cmd, "e") == 0)) {
282: char *glvn;
283: char key[STRLEN];
284: char res[STRLEN];
285:
286: if (dbg_argc < 2) {
287: printf ("debug: syntax error\n");
288: }
289: else {
290: glvn = dbarg;
291: name_to_key (key, glvn, STRLEN - 1);
292: if (key[0] != '^') {
293: symtab (get_sym, key, res);
294: }
295: else {
296: if (key[1] != '$') {
297: global (get_sym, key, res);
298: }
299: else {
300: ssvn (get_sym, key, res);
301: }
302: }
303:
304: if (merr () != OK) {
305: if (merr () == M6) {
306: printf ("examine: local variable %s is not defined\n", glvn);
307: merr_clear ();
308: }
309: else if (merr () == M7) {
310: printf ("examine: global variable %s is not defined\n", glvn);
311: merr_clear ();
312: }
313: else {
314: printf ("examine: error retrieving %s\n", glvn);
315: merr_clear ();
316: }
317: }
318: else {
319: stcnv_m2c (res);
320: printf ("%s=\"%s\"\n", glvn, res);
321: }
322:
323: }
324: }
325: else if ((strcmp (dbg_cmd, "trace") == 0) || (strcmp (dbg_cmd, "t") == 0)) {
326: if (trace_mode == 0) {
327: trace_mode = 1;
328: printf ("debug: trace on\n");
329: }
330: else {
331: trace_mode = 0;
332: printf ("debug: trace off\n");
333: }
334: ssvn_job_update ();
335: }
1.6 snw 336: else if ((strcmp (dbg_cmd, "step") == 0) || (strcmp (dbg_cmd, "s") == 0)) {
337: stepmode = STEPMODE_CMD;
338: return TRUE;
339: }
340: else if ((strcmp (dbg_cmd, "next") == 0) || (strcmp (dbg_cmd, "n") == 0)) {
341: stepmode = STEPMODE_LINE;
342: return TRUE;
343: }
344: else if ((strcmp (dbg_cmd, "continue") == 0) || (strcmp (dbg_cmd, "cont") == 0) || (strcmp(dbg_cmd, "c") == 0)) {
345: stepmode = STEPMODE_CONT;
346: return TRUE;
347: }
348: else if ((strcmp (dbg_cmd, "backtrace") == 0) || (strcmp (dbg_cmd, "bt") == 0)) {
1.9 snw 349: char tmpbuf[1024];
1.7 snw 350: char ecbuf[256];
1.10 snw 351: char m_cmd[10];
1.9 snw 352: char lref[1024];
353: char bt_mcode[1024];
1.7 snw 354:
1.10 snw 355: printf ("%-10s%-10s%s\n", "$STACK", "COMMAND", "PLACE");
356: printf ("%-10s%-10s%s\n", "======", "=======", "=====");
1.6 snw 357:
358:
359: for (i = 1; i <= nstx; i++) getraddress (callerr[i], i);
360: for (i = nstx; i > 0; i--) {
1.7 snw 361:
1.6 snw 362: stcpy (tmpbuf, callerr[i]);
363: stcnv_m2c (tmpbuf);
1.10 snw 364: strcpy (ecbuf, &(tmpbuf[1]));
365:
366: for (j = 0; j < strlen (ecbuf); j++) {
367: if (ecbuf[j] == ')') {
368: ecbuf[j] = '\0';
369: break;
370: }
371: }
372:
373: switch (ecbuf[0]) {
374: case 'F':
375: sprintf (m_cmd, "FOR");
376: break;
377: case 'D':
378: sprintf (m_cmd, "DO");
379: break;
380: }
381:
1.7 snw 382:
1.10 snw 383: printf ("%-10d%-10s%s\n", i, m_cmd, &(tmpbuf[3]));
1.7 snw 384: stcpy (lref, &(tmpbuf[3]));
385: stcnv_m2c (lref);
1.8 snw 386: if (routine_get_line (lref, bt_mcode) != NULL) {
1.7 snw 387: stcnv_m2c (bt_mcode);
1.10 snw 388: printf ("%-20s%s\n", " ", bt_mcode);
1.7 snw 389: }
1.6 snw 390: }
1.10 snw 391: stcpy (ecbuf, etrap);
392: stcnv_m2c (ecbuf);
393: printf ("ETRAP:\t%s\n", ecbuf);
394: stcpy (ecbuf, ztrap[nstx]);
395: stcnv_m2c (ecbuf);
396: printf ("ZTRAP:\t%s\n", ecbuf);
397:
1.6 snw 398: }
1.12 snw 399: else if ((strcmp (dbg_cmd, "halt") == 0) || (strcmp (dbg_cmd, "h") == 0)) {
400: printf ("debug: forcing halt\n");
401: exit (0);
402: }
403: else if ((strcmp (dbg_cmd, "watch") == 0) || (strcmp (dbg_cmd, "w") == 0)) {
404: char watchmode;
405: char *glvn;
406:
407: if (dbg_argc == 1) {
408: switch (dbg_enable_watch) {
409: case 0:
410: dbg_enable_watch = 1;
411: printf ("debug: watchpoints enabled\n");
412: break;
413: case 1:
414: dbg_enable_watch = 0;
415: printf ("debug: watchpoints disabled\n");
416: break;
417: }
418: }
419: else {
420: watchmode = dbarg[0];
421: glvn = &(dbarg[1]);
422:
423: switch (watchmode) {
424: case '+':
425: dbg_add_watch (glvn);
426: break;
427: case '-':
428: dbg_remove_watch (glvn);
429: break;
430: case '?':
431: dbg_dump_watch (glvn);
432: break;
433: default:
434: printf ("debug: watch mode must be +, -, or ?\n");
435: break;
436: }
437:
438: set_io (UNIX);
439: }
440: }
1.5 snw 441: else {
442: printf ("DEBUG: invalid command\r\n");
443: }
444:
445: }
446:
447: return TRUE;
448: #else
449: return FALSE;
450: #endif
451:
452: }
453:
1.1 snw 454: dbg_watch *dbg_add_watch (char *varnam)
455: {
456: register int i;
457: int index = -1;
458: short found = 0;
459: dbg_watch *w;
460:
461: if ((w = dbg_find_watch (varnam)) != NULL) {
462: set_io (UNIX);
463: fprintf (stderr, "You are already watching '%s' (changed %d times).\n", dbg_get_watch_name (w->varnam), w->chgct);
464: set_io (MUMPS);
465: return NULL;
466: }
467:
468: for (i = 0; i < MAXWATCH; i++) {
469: if (dbg_watchlist[i].varnam == NULL) {
470: found++;
471: index = i;
472: break;
473: }
474: }
475:
476: if (!found) {
477: set_io (UNIX);
478: fprintf (stderr, "No free watchlist entries available. Try removing an existing watchpoint first.\n");
479: set_io (MUMPS);
480:
481: return NULL;
482: }
483:
1.12 snw 484: if ((dbg_watchlist[index].varnam = (char *) calloc (256, sizeof (char))) == NULL) {
1.1 snw 485: set_io (UNIX);
486: fprintf (stderr, "Could not allocate memory for the new watchlist entry.\n");
487: set_io (MUMPS);
488:
489: return NULL;
490: }
491:
492: strcpy (dbg_watchlist[index].varnam, varnam);
493: dbg_watchlist[index].chgct = 0;
494:
495: set_io (UNIX);
1.12 snw 496: fprintf (stderr, "Added '%s' to the watchlist.\n", varnam);
1.1 snw 497: set_io (MUMPS);
498:
499: return NULL;
500:
501: }
502:
503: void dbg_dump_watchlist (void)
504: {
505: register int i;
506:
507: for (i = 0; i < MAXWATCH; i++) {
1.11 snw 508: if (dbg_watchlist[i].firect) {
509: dbg_dump_watch (dbg_watchlist[i].varnam);
510: }
1.1 snw 511: }
512:
513: dbg_pending_watches = 0;
514: }
515:
516:
517: void dbg_remove_watch (char *varnam)
518: {
519: dbg_watch *w;
520:
521: if ((w = dbg_find_watch (varnam)) == NULL) {
522: set_io (UNIX);
523: fprintf (stderr, "'%s' is not being watched.\n", dbg_get_watch_name (varnam));
524: set_io (MUMPS);
525:
526: return;
527: }
528:
529: free (w->varnam);
530:
531: w->chgct = 0;
532: w->firect = 0;
533:
534: set_io (UNIX);
535: printf ("Removed '%s' from the watchlist.\n", dbg_get_watch_name (varnam));
536: set_io (MUMPS);
537:
538: return;
539: }
540:
541: void dbg_dump_watch (char *varnam)
542: {
543: char *ddwbuf;
544: dbg_watch *w;
545:
546: ddwbuf = (char *) malloc (STRLEN * sizeof (char));
547: NULLPTRCHK(ddwbuf,"dbg_dump_watch");
548:
549: if ((w = dbg_find_watch (varnam)) == NULL) {
550: set_io (UNIX);
551: fprintf (stderr, "'%s' is not being watched.\n", dbg_get_watch_name (varnam));
552: set_io (MUMPS);
553:
554: return;
555: }
556:
557: w->firect = 0;
558:
559: if (varnam[0] != '^') {
560: symtab (get_sym, varnam, ddwbuf);
561: }
562: else {
563: if (varnam[1] == '$') {
564: ssvn (get_sym, varnam, ddwbuf);
565: }
566: else {
567: global (get_sym, varnam, ddwbuf);
568: }
569: }
570:
571: stcnv_m2c (ddwbuf);
572:
573: set_io (UNIX);
574: printf (">> WATCHPOINT: %s => '%s' (changed %d times)\n", dbg_get_watch_name (varnam), ddwbuf, w->chgct);
575: set_io (MUMPS);
576:
577: free (ddwbuf);
578:
579: }
580:
581: dbg_watch *dbg_find_watch (char *varnam)
582: {
583: register int i;
584:
585:
586: for (i = 0; i < MAXWATCH; i++) {
587: if (dbg_watchlist[i].varnam != NULL) {
588: if (strcmp (varnam, dbg_watchlist[i].varnam) == 0) {
589: return &(dbg_watchlist[i]);
590: }
591:
592: }
593: }
594:
595: return NULL;
596: }
597:
598: char *dbg_get_watch_name (char *varnam)
599: {
600: freem_ref_t *r;
601: char *s;
602:
603: r = (freem_ref_t *) malloc (sizeof (freem_ref_t));
604: NULLPTRCHK(r,"dbg_get_watch_name");
605:
606: s = (char *) malloc (STRLEN * sizeof (char));
607: NULLPTRCHK(s,"dbg_get_watch_name");
608:
609: mref_init (r, MREF_RT_LOCAL, "");
610: internal_to_mref (r, varnam);
611: mref_to_external (r, s);
612:
613: free (r);
614:
615: return s;
616:
617: }
618:
619: void dbg_fire_watch (char *varnam) {
620:
1.11 snw 621: dbg_watch *w;
622:
623: if ((w = dbg_find_watch (varnam)) == NULL) {
624: return;
625: }
626:
627: w->chgct++;
628: w->firect++;
629: dbg_pending_watches++;
630:
1.1 snw 631: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>