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>