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