/* * Copyright (c) 2001 by The XFree86 Project, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the XFree86 Project shall * not be used in advertising or otherwise to promote the sale, use or other * dealings in this Software without prior written authorization from the * XFree86 Project. * * Author: Paulo César Pereira de Andrade */ /* $XFree86: xc/programs/xedit/lisp/debugger.c,v 1.24tsi Exp $ */ #include #include "lisp/io.h" #include "lisp/debugger.h" #include "lisp/write.h" #ifdef DEBUGGER #define DebuggerHelp 0 #define DebuggerAbort 1 #define DebuggerBacktrace 2 #define DebuggerContinue 3 #define DebuggerFinish 4 #define DebuggerFrame 5 #define DebuggerNext 6 #define DebuggerPrint 7 #define DebuggerStep 8 #define DebuggerBreak 9 #define DebuggerDelete 10 #define DebuggerDown 11 #define DebuggerUp 12 #define DebuggerInfo 13 #define DebuggerWatch 14 #define DebuggerInfoBreakpoints 0 #define DebuggerInfoBacktrace 1 /* * Prototypes */ static char *format_integer(int); static void LispDebuggerCommand(LispObj *obj); /* * Initialization */ static struct { const char *name; int action; } const commands[] = { {"help", DebuggerHelp}, {"abort", DebuggerAbort}, {"backtrace", DebuggerBacktrace}, {"b", DebuggerBreak}, {"break", DebuggerBreak}, {"bt", DebuggerBacktrace}, {"continue", DebuggerContinue}, {"d", DebuggerDelete}, {"delete", DebuggerDelete}, {"down", DebuggerDown}, {"finish", DebuggerFinish}, {"frame", DebuggerFrame}, {"info", DebuggerInfo}, {"n", DebuggerNext}, {"next", DebuggerNext}, {"print", DebuggerPrint}, {"run", DebuggerContinue}, {"s", DebuggerStep}, {"step", DebuggerStep}, {"up", DebuggerUp}, {"watch", DebuggerWatch}, }; static struct { const char *name; int subaction; } const info_commands[] = { {"breakpoints", DebuggerInfoBreakpoints}, {"stack", DebuggerInfoBacktrace}, {"watchpoints", DebuggerInfoBreakpoints}, }; static const char *debugger_help = "Available commands are:\n\ \n\ help - This message.\n\ abort - Abort the current execution, and return to toplevel.\n\ backtrace, bt - Print backtrace.\n\ b, break - Set breakpoint at function name argument.\n\ continue - Continue execution.\n\ d, delete - Delete breakpoint(s), all breakpoint if no arguments given.\n\ down - Set environment to frame called by the current one.\n\ finish - Executes until current form is finished.\n\ frame - Set environment to selected frame.\n\ info - Prints information about the debugger state.\n\ n, next - Evaluate next form.\n\ print - Print value of variable name argument.\n\ run - Continue execution.\n\ s, step - Evaluate next form, stopping on any subforms.\n\ up - Set environment to frame that called the current one.\n\ \n\ Commands may be abbreviated.\n"; static const char *debugger_info_help = "Available subcommands are:\n\ \n\ breakpoints - List and prints status of breakpoints, and watchpoints.\n\ stack - Backtrace of stack.\n\ watchpoints - List and prints status of watchpoints, and breakpoints.\n\ \n\ Subcommands may be abbreviated.\n"; /* Debugger variables layout (if you change it, update description): * * DBG * is a macro for lisp__data.dbglist * is a NIL terminated list * every element is a list in the format (NOT NIL terminated): * (list* NAM ARG ENV HED LEX) * where * NAM is an ATOM for the function/macro name * or NIL for lambda expressions * ARG is NAM arguments (a LIST) * ENV is the value of lisp__data.stack.base (a FIXNUM) * LEN is the value of lisp__data.env.length (a FIXNUM) * LEX is the value of lisp__data.env.lex (a FIXNUM) * new elements are added to the beggining of the DBG list * * BRK * is macro for lisp__data.brklist * is a NIL terminated list * every element is a list in the format (NIL terminated): * (list NAM IDX TYP HIT VAR VAL FRM) * where * NAM is an ATOM for the name of the object at * wich the breakpoint was added * IDX is a FIXNUM, the breakpoint number * must be stored, as breakpoints may be deleted * TYP is a FIXNUM that must be an integer of enum LispBreakType * HIT is a FIXNUM, with the number of times this breakpoint was * hitted. * VAR variable to watch a SYMBOL (not needed for breakpoints) * VAL value of watched variable (not needed for breakpoints) * FRM frame where variable started being watched * (not needed for breakpoints) * new elements are added to the end of the list */ /* * Implementation */ void LispDebugger(LispDebugCall call, LispObj *name, LispObj *arg) { int force = 0; LispObj *obj, *prev; switch (call) { case LispDebugCallBegin: ++lisp__data.debug_level; GCDisable(); DBG = CONS(CONS(name, CONS(arg, CONS(FIXNUM(lisp__data.stack.base), CONS(FIXNUM(lisp__data.env.length), FIXNUM(lisp__data.env.lex))))), DBG); GCEnable(); for (obj = BRK; obj != NIL; obj = CDR(obj)) if (ATOMID(CAR(CAR(obj))) == ATOMID(name) && FIXNUM_VALUE(CAR(CDR(CDR(CAR(obj))))) == LispDebugBreakFunction) break; if (obj != NIL) { long counter; /* if not at a fresh line */ if (LispGetColumn(NIL)) LispFputc(Stdout, '\n'); LispFputs(Stdout, "BREAK #"); LispWriteObject(NIL, CAR(CDR(CAR(obj)))); LispFputs(Stdout, "> ("); LispWriteObject(NIL, CAR(CAR(DBG))); LispFputc(Stdout, ' '); LispWriteObject(NIL, CAR(CDR(CAR(DBG)))); LispFputs(Stdout, ")\n"); force = 1; /* update hits counter */ counter = FIXNUM_VALUE(CAR(CDR(CDR(CDR(CAR(obj)))))); CAR(CDR(CDR(CDR(CAR(obj))))) FIXNUM(counter + 1); } break; case LispDebugCallEnd: DBG = CDR(DBG); if (lisp__data.debug_level < lisp__data.debug_step) lisp__data.debug_step = lisp__data.debug_level; --lisp__data.debug_level; break; case LispDebugCallFatal: LispDebuggerCommand(NIL); return; case LispDebugCallWatch: break; } /* didn't return, check watchpoints */ if (call == LispDebugCallEnd || call == LispDebugCallWatch) { watch_again: for (prev = obj = BRK; obj != NIL; prev = obj, obj = CDR(obj)) { if (FIXNUM_VALUE(CAR(CDR(CDR(CAR(obj))))) == LispDebugBreakVariable) { /* the variable */ LispObj *wat = CAR(CDR(CDR(CDR(CDR(CAR(obj)))))); void *sym = LispGetVarAddr(CAAR(obj)); LispObj *frm = CAR(CDR(CDR(CDR(CDR(CDR(CDR(CAR(obj)))))))); if ((sym == NULL && lisp__data.debug_level <= 0) || (sym != wat->data.opaque.data && FIXNUM_VALUE(frm) > lisp__data.debug_level)) { LispFputs(Stdout, "WATCH #"); LispFputs(Stdout, format_integer(FIXNUM_VALUE(CAR(CDR(CAR(obj)))))); LispFputs(Stdout, "> "); LispFputs(Stdout, STRPTR(CAR(CAR(obj)))); LispFputs(Stdout, " deleted. Variable does not exist anymore.\n"); /* force debugger to stop */ force = 1; if (obj == prev) { BRK = CDR(BRK); goto watch_again; } else RPLACD(prev, CDR(obj)); obj = prev; } else { /* current value */ LispObj *cur = *(LispObj**)wat->data.opaque.data; /* last value */ LispObj *val = CAR(CDR(CDR(CDR(CDR(CDR(CAR(obj))))))); if (XEQUAL(val, cur) == NIL) { long counter; LispFputs(Stdout, "WATCH #"); LispFputs(Stdout, format_integer(FIXNUM_VALUE(CAR(CDR(CAR(obj)))))); LispFputs(Stdout, "> "); LispFputs(Stdout, STRPTR(CAR(CAR(obj)))); LispFputc(Stdout, '\n'); LispFputs(Stdout, "OLD: "); LispWriteObject(NIL, val); LispFputs(Stdout, "\nNEW: "); LispWriteObject(NIL, cur); LispFputc(Stdout, '\n'); /* update current value */ CAR(CDR(CDR(CDR(CDR(CDR(CAR(obj))))))) = cur; /* update hits counter */ counter = FIXNUM_VALUE(CAR(CDR(CDR(CDR(CAR(obj)))))); CAR(CDR(CDR(CDR(CAR(obj))))) = FIXNUM(counter + 1); /* force debugger to stop */ force = 1; } } } } if (call == LispDebugCallWatch) /* special call, just don't keep gc protected variables that may be * using a lot of memory... */ return; } switch (lisp__data.debug) { case LispDebugUnspec: LispDebuggerCommand(NIL); goto debugger_done; case LispDebugRun: if (force) LispDebuggerCommand(NIL); goto debugger_done; case LispDebugFinish: if (!force && (call != LispDebugCallEnd || lisp__data.debug_level != lisp__data.debug_step)) goto debugger_done; break; case LispDebugNext: if (call == LispDebugCallBegin) { if (!force && lisp__data.debug_level != lisp__data.debug_step) goto debugger_done; } else if (call == LispDebugCallEnd) { if (!force && lisp__data.debug_level >= lisp__data.debug_step) goto debugger_done; } break; case LispDebugStep: break; } if (call == LispDebugCallBegin) { LispFputc(Stdout, '#'); LispFputs(Stdout, format_integer(lisp__data.debug_level)); LispFputs(Stdout, "> ("); LispWriteObject(NIL, CAR(CAR(DBG))); LispFputc(Stdout, ' '); LispWriteObject(NIL, CAR(CDR(CAR(DBG)))); LispFputs(Stdout, ")\n"); LispDebuggerCommand(NIL); } else if (call == LispDebugCallEnd) { LispFputc(Stdout, '#'); LispFputs(Stdout, format_integer(lisp__data.debug_level + 1)); LispFputs(Stdout, "= "); LispWriteObject(NIL, arg); LispFputc(Stdout, '\n'); LispDebuggerCommand(NIL); } else if (force) LispDebuggerCommand(arg); debugger_done: return; } static void LispDebuggerCommand(LispObj *args) { LispObj *obj, *frm, *curframe; int i = 0, frame, matches, action = -1, subaction = 0; char *cmd, *arg, *ptr, line[256]; int envbase = lisp__data.stack.base, envlen = lisp__data.env.length, envlex = lisp__data.env.lex; frame = lisp__data.debug_level; curframe = CAR(DBG); line[0] = '\0'; arg = line; for (;;) { LispFputs(Stdout, DBGPROMPT); LispFflush(Stdout); if (LispFgets(Stdin, line, sizeof(line)) == NULL) { LispFputc(Stdout, '\n'); return; } /* get command */ ptr = line; while (*ptr && isspace(*ptr)) ++ptr; cmd = ptr; while (*ptr && !isspace(*ptr)) ++ptr; if (*ptr) *ptr++ = '\0'; if (*cmd) { /* if *cmd is nul, then arg may be still set */ /* get argument(s) */ while (*ptr && isspace(*ptr)) ++ptr; arg = ptr; /* goto end of line */ if (*ptr) { while (*ptr) ++ptr; --ptr; while (*ptr && isspace(*ptr)) --ptr; if (*ptr) *++ptr = '\0'; } } if (*cmd == '\0') { if (action < 0) { if (lisp__data.debug == LispDebugFinish) action = DebuggerFinish; else if (lisp__data.debug == LispDebugNext) action = DebuggerNext; else if (lisp__data.debug == LispDebugStep) action = DebuggerStep; else if (lisp__data.debug == LispDebugRun) action = DebuggerContinue; else continue; } } else { for (i = matches = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { const char *str = commands[i].name; ptr = cmd; while (*ptr && *ptr == *str) { ++ptr; ++str; } if (*ptr == '\0') { action = commands[i].action; if (*str == '\0') { matches = 1; break; } ++matches; } } if (matches == 0) { LispFputs(Stdout, "* Command unknown: "); LispFputs(Stdout, cmd); LispFputs(Stdout, ". Type help for help.\n"); continue; } else if (matches > 1) { LispFputs(Stdout, "* Command is ambiguous: "); LispFputs(Stdout, cmd); LispFputs(Stdout, ". Type help for help.\n"); continue; } } switch (action) { case DebuggerHelp: LispFputs(Stdout, debugger_help); break; case DebuggerInfo: if (*arg == '\0') { LispFputs(Stdout, debugger_info_help); break; } for (i = matches = 0; i < sizeof(info_commands) / sizeof(info_commands[0]); i++) { const char *str = info_commands[i].name; ptr = arg; while (*ptr && *ptr == *str) { ++ptr; ++str; } if (*ptr == '\0') { subaction = info_commands[i].subaction; if (*str == '\0') { matches = 1; break; } ++matches; } } if (matches == 0) { LispFputs(Stdout, "* Command unknown: "); LispFputs(Stdout, arg); LispFputs(Stdout, ". Type info for help.\n"); continue; } else if (matches > 1) { LispFputs(Stdout, "* Command is ambiguous: "); LispFputs(Stdout, arg); LispFputs(Stdout, ". Type info for help.\n"); continue; } switch (subaction) { case DebuggerInfoBreakpoints: LispFputs(Stdout, "Num\tHits\tType\t\tWhat\n"); for (obj = BRK; obj != NIL; obj = CDR(obj)) { /* breakpoint number */ LispFputc(Stdout, '#'); LispWriteObject(NIL, CAR(CDR(CAR(obj)))); /* number of hits */ LispFputc(Stdout, '\t'); LispWriteObject(NIL, CAR(CDR(CDR(CDR(CAR(obj)))))); /* breakpoint type */ LispFputc(Stdout, '\t'); switch ((int)FIXNUM_VALUE(CAR(CDR(CDR(CAR(obj)))))) { case LispDebugBreakFunction: LispFputs(Stdout, "Function"); break; case LispDebugBreakVariable: LispFputs(Stdout, "Variable"); break; } /* breakpoint object */ LispFputc(Stdout, '\t'); LispWriteObject(NIL, CAR(CAR(obj))); LispFputc(Stdout, '\n'); } break; case DebuggerInfoBacktrace: goto debugger_print_backtrace; } break; case DebuggerAbort: while (lisp__data.mem.level) { --lisp__data.mem.level; if (lisp__data.mem.mem[lisp__data.mem.level]) free(lisp__data.mem.mem[lisp__data.mem.level]); } lisp__data.mem.index = 0; LispTopLevel(); if (!lisp__data.running) { LispMessage("*** Fatal: nowhere to longjmp."); abort(); } /* don't need to restore environment */ siglongjmp(lisp__data.jmp, 1); /*NOTREACHED*/ break; case DebuggerBreak: for (ptr = arg; *ptr; ptr++) { if (isspace(*ptr)) break; else *ptr = toupper(*ptr); } if (!*arg || *ptr || strchr(arg, '(') || strchr(arg, '(') || strchr(arg, ';')) { LispFputs(Stdout, "* Bad function name '"); LispFputs(Stdout, arg); LispFputs(Stdout, "' specified.\n"); } else { for (obj = frm = BRK; obj != NIL; frm = obj, obj = CDR(obj)) ; i = lisp__data.debug_break; ++lisp__data.debug_break; GCDisable(); obj = CONS(ATOM(arg), CONS(FIXNUM(i), CONS(FIXNUM(LispDebugBreakFunction), CONS(FIXNUM(0), NIL)))); if (BRK == NIL) BRK = CONS(obj, NIL); else RPLACD(frm, CONS(obj, NIL)); GCEnable(); } break; case DebuggerWatch: { void *sym; int vframe; LispObj *val, *atom; /* make variable name uppercase, an ATOM */ ptr = arg; while (*ptr) { *ptr = toupper(*ptr); ++ptr; } atom = ATOM(arg); val = LispGetVar(atom); if (val == NULL) { LispFputs(Stdout, "* No variable named '"); LispFputs(Stdout, arg); LispFputs(Stdout, "' in the selected frame.\n"); break; } /* variable is available at the current frame */ sym = LispGetVarAddr(atom); /* find the lowest frame where the variable is visible */ vframe = 0; if (frame > 0) { for (; vframe < frame; vframe++) { for (frm = DBG, i = lisp__data.debug_level; i > vframe; frm = CDR(frm), i--) ; obj = CAR(frm); lisp__data.stack.base = FIXNUM_VALUE(CAR(CDR(CDR(obj)))); lisp__data.env.length = FIXNUM_VALUE(CAR(CDR(CDR(CDR(obj))))); lisp__data.env.lex = FIXNUM_VALUE(CDR(CDR(CDR(CDR(obj))))); if (LispGetVarAddr(atom) == sym) /* got variable initial frame */ break; } vframe = i; if (vframe != frame) { /* restore environment */ for (frm = DBG, i = lisp__data.debug_level; i > frame; frm = CDR(frm), i--) ; obj = CAR(frm); lisp__data.stack.base = FIXNUM_VALUE(CAR(CDR(CDR(obj)))); lisp__data.env.length = FIXNUM_VALUE(CAR(CDR(CDR(CDR(obj))))); lisp__data.env.lex = FIXNUM_VALUE(CDR(CDR(CDR(CDR(obj))))); } } i = lisp__data.debug_break; ++lisp__data.debug_break; for (obj = frm = BRK; obj != NIL; frm = obj, obj = CDR(obj)) ; GCDisable(); obj = CONS(atom, /* NAM */ CONS(FIXNUM(i), /* IDX */ CONS(FIXNUM(LispDebugBreakVariable), /* TYP */ CONS(FIXNUM(0), /* HIT */ CONS(OPAQUE(sym, 0), /* VAR */ CONS(val, /* VAL */ CONS(FIXNUM(vframe),/* FRM */ NIL))))))); /* add watchpoint */ if (BRK == NIL) BRK = CONS(obj, NIL); else RPLACD(frm, CONS(obj, NIL)); GCEnable(); } break; case DebuggerDelete: if (*arg == 0) { int confirm = 0; for (;;) { int ch; LispFputs(Stdout, "* Delete all breakpoints? (y or n) "); LispFflush(Stdout); if ((ch = LispFgetc(Stdin)) == '\n') continue; while ((i = LispFgetc(Stdin)) != '\n' && i != EOF) ; if (tolower(ch) == 'n') break; else if (tolower(ch) == 'y') { confirm = 1; break; } } if (confirm) BRK = NIL; } else { for (ptr = arg; *ptr;) { while (*ptr && isdigit(*ptr)) ++ptr; if (*ptr && !isspace(*ptr)) { *ptr = '\0'; LispFputs(Stdout, "* Bad breakpoint number '"); LispFputs(Stdout, arg); LispFputs(Stdout, "' specified.\n"); break; } i = atoi(arg); for (obj = frm = BRK; frm != NIL; obj = frm, frm = CDR(frm)) if (FIXNUM_VALUE(CAR(CDR(CAR(frm)))) == i) break; if (frm == NIL) { LispFputs(Stdout, "* No breakpoint number "); LispFputs(Stdout, arg); LispFputs(Stdout, " available.\n"); break; } if (obj == frm) BRK = CDR(BRK); else RPLACD(obj, CDR(frm)); while (*ptr && isspace(*ptr)) ++ptr; arg = ptr; } } break; case DebuggerFrame: i = -1; ptr = arg; if (*ptr) { i = 0; while (*ptr && isdigit(*ptr)) { i *= 10; i += *ptr - '0'; ++ptr; } if (*ptr) { LispFputs(Stdout, "* Frame identifier must " "be a positive number.\n"); break; } } else goto debugger_print_frame; if (i >= 0 && i <= lisp__data.debug_level) goto debugger_new_frame; LispFputs(Stdout, "* No such frame "); LispFputs(Stdout, format_integer(i)); LispFputs(Stdout, ".\n"); break; case DebuggerDown: if (frame + 1 > lisp__data.debug_level) { LispFputs(Stdout, "* Cannot go down.\n"); break; } i = frame + 1; goto debugger_new_frame; break; case DebuggerUp: if (frame == 0) { LispFputs(Stdout, "* Cannot go up.\n"); break; } i = frame - 1; goto debugger_new_frame; break; case DebuggerPrint: ptr = arg; while (*ptr) { *ptr = toupper(*ptr); ++ptr; } obj = LispGetVar(ATOM(arg)); if (obj != NULL) { LispWriteObject(NIL, obj); LispFputc(Stdout, '\n'); } else { LispFputs(Stdout, "* No variable named '"); LispFputs(Stdout, arg); LispFputs(Stdout, "' in the selected frame.\n"); } break; case DebuggerBacktrace: debugger_print_backtrace: if (DBG == NIL) { LispFputs(Stdout, "* No stack.\n"); break; } DBG = LispReverse(DBG); for (obj = DBG, i = 0; obj != NIL; obj = CDR(obj), i++) { frm = CAR(obj); LispFputc(Stdout, '#'); LispFputs(Stdout, format_integer(i)); LispFputs(Stdout, "> ("); LispWriteObject(NIL, CAR(frm)); LispFputc(Stdout, ' '); LispWriteObject(NIL, CAR(CDR(frm))); LispFputs(Stdout, ")\n"); } DBG = LispReverse(DBG); break; case DebuggerContinue: lisp__data.debug = LispDebugRun; goto debugger_command_done; case DebuggerFinish: if (lisp__data.debug != LispDebugFinish) { lisp__data.debug_step = lisp__data.debug_level - 2; lisp__data.debug = LispDebugFinish; } else lisp__data.debug_step = lisp__data.debug_level - 1; goto debugger_command_done; case DebuggerNext: if (lisp__data.debug != LispDebugNext) { lisp__data.debug = LispDebugNext; lisp__data.debug_step = lisp__data.debug_level + 1; } goto debugger_command_done; case DebuggerStep: lisp__data.debug = LispDebugStep; goto debugger_command_done; } continue; debugger_new_frame: /* goto here with i as the new frame value, after error checking */ if (i != frame) { frame = i; for (frm = DBG, i = lisp__data.debug_level; i > frame; frm = CDR(frm), i--) ; curframe = CAR(frm); lisp__data.stack.base = FIXNUM_VALUE(CAR(CDR(CDR(curframe)))); lisp__data.env.length = FIXNUM_VALUE(CAR(CDR(CDR(CDR(curframe))))); lisp__data.env.lex = FIXNUM_VALUE(CDR(CDR(CDR(CDR(curframe))))); } debugger_print_frame: LispFputc(Stdout, '#'); LispFputs(Stdout, format_integer(frame)); LispFputs(Stdout, "> ("); LispWriteObject(NIL, CAR(curframe)); LispFputc(Stdout, ' '); LispWriteObject(NIL, CAR(CDR(curframe))); LispFputs(Stdout, ")\n"); } debugger_command_done: lisp__data.stack.base = envbase; lisp__data.env.length = envlen; lisp__data.env.lex = envlex; } static char * format_integer(int integer) { static char buffer[16]; sprintf(buffer, "%d", integer); return (buffer); } #endif /* DEBUGGER */