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