File:  [Coherent Logic Development] / freem / src / mdebug.c
Revision 1.13: download - view: text, annotated - select for diffs
Tue May 20 18:07:41 2025 UTC (2 months, 1 week ago) by snw
Branches: MAIN
CVS tags: HEAD
Add completion to debugger

    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>