home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-07-28 | 33.1 KB | 1,026 lines |
- ;;; -*- Mode: Lisp; Package: Debug; Log: code.log -*-
- ;;;
- ;;; **********************************************************************
- ;;; This code was written as part of the CMU Common Lisp project at
- ;;; Carnegie Mellon University, and has been placed in the public domain.
- ;;; If you want to use this code or any part of CMU Common Lisp, please contact
- ;;; Scott Fahlman or slisp-group@cs.cmu.edu.
- ;;;
- (ext:file-comment
- "$Header: debug.lisp,v 1.30.1.1 92/07/28 19:51:37 ram Exp $")
- ;;;
- ;;; **********************************************************************
- ;;;
- ;;; CMU Common Lisp Debugger. This includes a basic command-line oriented
- ;;; debugger interface as well as support for Hemlock to deliver debugger
- ;;; commands to a slave Lisp.
- ;;;
- ;;; Written by Bill Chiles.
- ;;;
-
- (in-package "DEBUG")
-
- (export '(internal-debug *in-the-debugger* backtrace *flush-debug-errors*
- *debug-print-level* *debug-print-length* *debug-prompt*
- *help-line-scroll-count* *stack-top-hint*
-
- *auto-eval-in-frame* var arg
-
- do-debug-command))
-
- (in-package "LISP")
- (export '(invoke-debugger *debugger-hook*))
-
- (in-package "DEBUG")
-
-
-
- ;;;; Variables, parameters, and constants.
-
- (defparameter *debug-print-level* 3
- "*PRINT-LEVEL* is bound to this value when debug prints a function call. If
- null, use *PRINT-LEVEL*")
-
- (defparameter *debug-print-length* 5
- "*PRINT-LENGTH* is bound to this value when debug prints a function call. If
- null, use *PRINT-LENGTH*.")
-
- (defvar *in-the-debugger* nil
- "This is T while in the debugger.")
-
- (defvar *debug-command-level* 0
- "Pushes and pops/exits inside the debugger change this.")
-
- (defvar *stack-top-hint* nil
- "If this is bound before the debugger is invoked, it is used as the stack
- top by the debugger.")
- (defvar *stack-top* nil)
- (defvar *real-stack-top* nil)
-
- (defvar *current-frame* nil)
- (defvar *current-code-location* nil)
-
- ;;; DEBUG-PROMPT -- Internal.
- ;;;
- ;;; This is the default for *debug-prompt*.
- ;;;
- (defun debug-prompt ()
- (let ((*standard-output* *debug-io*))
- (terpri)
- (prin1 (di:frame-number *current-frame*))
- (dotimes (i *debug-command-level*) (princ "]"))
- (princ " ")
- (force-output)))
-
- (defparameter *debug-prompt* #'debug-prompt
- "This is a function of no arguments that prints the debugger prompt
- on *debug-io*.")
-
- (defconstant debug-help-string
- "
- The prompt is right square brackets, the number indicating how many
- recursive command loops you are in.
- Debug commands do not affect * and friends, but evaluation in the debug loop
- do affect these variables.
- Any command may be uniquely abbreviated.
-
- Getting in and out of DEBUG:
- Q throws to top level.
- GO calls CONTINUE which tries to proceed with the restart 'continue.
- RESTART invokes restart numbered as shown (prompt if not given).
- ERROR prints the error condition and restart cases.
- FLUSH toggles *flush-debug-errors*, which is initially t.
-
- The name of any restart, or its number, is a valid command, and is the same
- as using RESTART to invoke that restart.
-
- Changing frames:
- U up frame D down frame T top frame B bottom frame
-
- F n goes to frame n.
-
- Inspecting frames:
- BACKTRACE [n] shows n frames going down the stack.
- L lists locals in current function.
- P, PP displays current function call.
- SOURCE [n] displays frame's source form with n levels of enclosing forms.
- VSOURCE [n] displays frame's source form without any ellipsis.
-
- Function and macro commands:
- (DEBUG:DEBUG-RETURN expression)
- returns expression's values from the current frame, exiting the debugger.
- (DEBUG:ARG n)
- returns the n'th argument, remaining in the debugger.
- (DEBUG:VAR string-or-symbol [id])
- returns the specified variable's value, remaining in the debugger.
-
- See the CMU Common Lisp User's Manual for more information.
- ")
-
-
-
- ;;;; Backtrace:
-
- ;;; BACKTRACE -- Public.
- ;;;
- (defun backtrace (&optional (count most-positive-fixnum)
- (*standard-output* *debug-io*))
- "Show a listing of the call stack going down from the current frame. In the
- debugger, the current frame is indicated by the prompt. Count is how many
- frames to show."
- (let ((*print-length* (or *debug-print-length* *print-length*))
- (*print-level* (or *debug-print-level* *print-level*)))
- (fresh-line *standard-output*)
- (do ((frame (if *in-the-debugger* *current-frame* (di:top-frame))
- (di:frame-down frame))
- (count count (1- count)))
- ((or (null frame) (zerop count))
- (values))
- (print-frame-call frame))))
-
-
- (eval-when (compile eval)
-
- ;;; LAMBDA-LIST-ELEMENT-DISPATCH -- Internal.
- ;;;
- ;;; This is a convenient way to express what to do for each type of lambda-list
- ;;; element.
- ;;;
- (defmacro lambda-list-element-dispatch (element &key required optional rest
- keyword deleted)
- `(etypecase ,element
- (di:debug-variable
- ,@required)
- (cons
- (ecase (car ,element)
- (:optional ,@optional)
- (:rest ,@rest)
- (:keyword ,@keyword)))
- (symbol
- (assert (eq ,element :deleted))
- ,@deleted)))
-
- (defmacro lambda-var-dispatch (variable location deleted valid other)
- (let ((var (gensym)))
- `(let ((,var ,variable))
- (cond ((eq ,var :deleted) ,deleted)
- ((eq (di:debug-variable-validity ,var ,location) :valid) ,valid)
- (t ,other)))))
-
- ) ;EVAL-WHEN
-
- ;;; PRINT-FRAME-CALL -- Internal.
- ;;;
- ;;; This prints a representation of the function call causing frame to exist.
- ;;; Verbosity indicates the level of information to output; zero indicates just
- ;;; printing the debug-function's name, and one indicates displaying call-like,
- ;;; one-liner format with argument values.
- ;;;
- (defun print-frame-call (frame &optional
- (*print-length* (or *debug-print-length*
- *print-length*))
- (*print-level* (or *debug-print-level*
- *print-level*))
- (verbosity 1))
- (ecase verbosity
- (0 (print frame))
- (1 (print-frame-call-1 frame))
- ((2 3 4 5))))
-
- ;;; This is used in constructing arg lists for debugger printing when the arg
- ;;; list is unavailable, some arg is unavailable or unused, etc.
- ;;;
- (defstruct (unprintable-object
- (:constructor make-unprintable-object (string))
- (:print-function (lambda (x s d)
- (declare (ignore d))
- (format s "#<~A>"
- (unprintable-object-string x)))))
- string)
-
- ;;; PRINT-FRAME-CALL-1 -- Internal.
- ;;;
- ;;; This prints frame with verbosity level 1. If we hit a rest-arg,
- ;;; then print as many of the values as possible,
- ;;; punting the loop over lambda-list variables since any other arguments
- ;;; will be in the rest-arg's list of values.
- ;;;
- (defun print-frame-call-1 (frame &optional (preceding-newline t))
- (let* ((d-fun (di:frame-debug-function frame))
- (loc (di:frame-code-location frame))
- (results (list (di:debug-function-name d-fun))))
- (handler-case
- (dolist (ele (di:debug-function-lambda-list d-fun))
- (lambda-list-element-dispatch ele
- :required ((push (frame-call-arg ele loc frame) results))
- :optional ((push (frame-call-arg (second ele) loc frame) results))
- :keyword ((push (second ele) results)
- (push (frame-call-arg (third ele) loc frame) results))
- :deleted ((push (frame-call-arg ele loc frame) results))
- :rest ((lambda-var-dispatch (second ele) loc
- nil
- (progn
- (setf results
- (append (reverse (di:debug-variable-value
- (second ele) frame))
- results))
- (return))
- (push (make-unprintable-object "unavaliable-rest-arg")
- results)))))
- (di:lambda-list-unavailable
- ()
- (push (make-unprintable-object "lambda-list-unavailable") results)))
- (when preceding-newline (terpri))
- (prin1 (nreverse results))
- (when (di:debug-function-kind d-fun)
- (write-char #\[)
- (prin1 (di:debug-function-kind d-fun))
- (write-char #\]))))
- ;;;
- (defun frame-call-arg (var location frame)
- (lambda-var-dispatch var location
- (make-unprintable-object "unused-arg")
- (di:debug-variable-value var frame)
- (make-unprintable-object "unavailable-arg")))
-
-
-
- ;;;; INVOKE-DEBUGGER.
-
- (defvar *debugger-hook* nil
- "This is either nil or a function of two arguments, a condition and the value
- of *debugger-hook*. This function can either handle the condition or return
- which causes the standard debugger to execute. The system passes the value
- of this variable to the function because it binds *debugger-hook* to nil
- around the invocation.")
-
- ;;; These are bound on each invocation of INVOKE-DEBUGGER.
- ;;;
- (defvar *debug-restarts*)
- (defvar *debug-condition*)
-
- ;;; INVOKE-DEBUGGER -- Public.
- ;;;
- (defun invoke-debugger (condition)
- "The CMU Common Lisp debugger. Type h for help."
- (when *debugger-hook*
- (let ((hook *debugger-hook*)
- (*debugger-hook* nil))
- (funcall hook condition hook)))
- (unix:unix-sigsetmask 0)
- (let* ((*debug-condition* condition)
- (*debug-restarts* (compute-restarts))
- (*standard-input* *debug-io*) ;in case of setq
- (*standard-output* *debug-io*) ;'' '' '' ''
- (*error-output* *debug-io*)
- ;; Rebind some printer control variables.
- (kernel:*current-level* 0)
- (*print-readably* nil)
- (*read-eval* t))
- (format *error-output* "~2&~A~2&" *debug-condition*)
- (show-restarts *debug-restarts* *error-output*)
- (internal-debug)))
-
- ;;; SHOW-RESTARTS -- Internal.
- ;;;
- (defun show-restarts (restarts &optional (s *error-output*))
- (when restarts
- (format s "~&Restarts:~%")
- (let ((count 0)
- (names-used '(nil))
- (max-name-len 0))
- (dolist (restart restarts)
- (let ((name (restart-name restart)))
- (when name
- (let ((len (length (princ-to-string name))))
- (when (> len max-name-len)
- (setf max-name-len len))))))
- (unless (zerop max-name-len)
- (incf max-name-len 3))
- (dolist (restart restarts)
- (let ((name (restart-name restart)))
- (cond ((member name names-used)
- (format s "~& ~2D: ~@VT~A~%" count max-name-len restart))
- (t
- (format s "~& ~2D: [~VA] ~A~%"
- count (- max-name-len 3) name restart)
- (push name names-used))))
- (incf count)))))
-
- ;;; INTERNAL-DEBUG -- Internal Interface.
- ;;;
- ;;; This calls DEBUG-LOOP, performing some simple initializations before doing
- ;;; so. INVOKE-DEBUGGER calls this to actually get into the debugger.
- ;;; CONDITIONS::ERROR-ERROR calls this in emergencies to get into a debug
- ;;; prompt as quickly as possible with as little risk as possible for stepping
- ;;; on whatever is causing recursive errors.
- ;;;
- (defun internal-debug ()
- (let ((*in-the-debugger* t)
- (*read-suppress* nil))
- (clear-input *debug-io*)
- (format *debug-io* "~2&Debug (type H for help)~%")
- (debug-loop)))
-
-
-
- ;;;; DEBUG-LOOP.
-
- (defvar *flush-debug-errors* t
- "When set, avoid calling INVOKE-DEBUGGER recursively when errors occur while
- executing in the debugger. The 'flush' command toggles this.")
-
- (defun debug-loop ()
- (let* ((*debug-command-level* (1+ *debug-command-level*))
- (*real-stack-top* (di:top-frame))
- (*stack-top* (or *stack-top-hint* *real-stack-top*))
- (*stack-top-hint* nil)
- (*current-frame* *stack-top*)
- (*current-code-location* nil))
- (handler-bind ((di:debug-condition #'(lambda (condition)
- (princ condition *debug-io*)
- (throw 'debug-loop-catcher nil))))
- (print-frame-call *current-frame*)
- (loop
- (catch 'debug-loop-catcher
- (handler-bind ((error #'(lambda (condition)
- (when *flush-debug-errors*
- (clear-input *debug-io*)
- (princ condition)
- (format t "~&Error flushed ...")
- (throw 'debug-loop-catcher nil)))))
- ;; Must bind level for restart function created by
- ;; WITH-SIMPLE-RESTART.
- (let ((level *debug-command-level*)
- (restart-commands (make-restart-commands)))
- (with-simple-restart (abort "Return to debug level ~D." level)
- (funcall *debug-prompt*)
- (let ((input (ext:get-stream-command *debug-io*)))
- (cond (input
- (let ((cmd-fun (debug-command-p
- (ext:stream-command-name input)
- restart-commands)))
- (cond
- ((not cmd-fun)
- (error "Unknown stream-command -- ~S." input))
- ((consp cmd-fun)
- (error "Ambiguous debugger command: ~S." cmd-fun))
- (t
- (apply cmd-fun (ext:stream-command-args input))))))
- (t
- (let* ((exp (read))
- (cmd-fun (debug-command-p exp restart-commands)))
- (cond ((not cmd-fun)
- (debug-eval-print exp))
- ((consp cmd-fun)
- (format t "~&Your command, ~S, is ambiguous:~%"
- exp)
- (dolist (ele cmd-fun)
- (format t " ~A~%" ele)))
- (t
- (funcall cmd-fun)))))))))))))))
-
- (defvar *auto-eval-in-frame* t
- "When set (the default), evaluations in the debugger's command loop occur
- relative to the current frame's environment without the need of debugger
- forms that explicitly control this kind of evaluation.")
-
- (defun debug-eval-print (exp)
- (setq +++ ++ ++ + + - - exp)
- (let* ((values (multiple-value-list
- (if (and (fboundp 'eval:internal-eval) *auto-eval-in-frame*)
- (di:eval-in-frame *current-frame* -)
- (eval -))))
- (*standard-output* *debug-io*))
- (fresh-line)
- (if values (prin1 (car values)))
- (dolist (x (cdr values))
- (fresh-line)
- (prin1 x))
- (setq /// // // / / values)
- (setq *** ** ** * * (car values))
- ;; Make sure nobody passes back an unbound marker.
- (unless (boundp '*)
- (setq * nil)
- (fresh-line)
- (princ "Setting * to NIL -- was unbound marker."))))
-
-
-
- ;;;; Debug loop functions.
-
- ;;; These commands are function, not really commands, so users can get their
- ;;; hands on the values returned.
- ;;;
-
- (eval-when (eval compile)
-
- (defmacro define-var-operation (ref-or-set &optional value-var)
- `(let* ((temp (etypecase name
- (symbol (di:debug-function-symbol-variables
- (di:frame-debug-function *current-frame*)
- name))
- (simple-string (di:ambiguous-debug-variables
- (di:frame-debug-function *current-frame*)
- name))))
- (location (di:frame-code-location *current-frame*))
- ;; Let's only deal with valid variables.
- (vars (remove-if-not #'(lambda (v)
- (eq (di:debug-variable-validity v location)
- :valid))
- temp)))
- (declare (list vars))
- (cond ((null vars)
- (error "No known valid variables match ~S." name))
- ((= (length vars) 1)
- ,(ecase ref-or-set
- (:ref
- '(di:debug-variable-value (car vars) *current-frame*))
- (:set
- `(setf (di:debug-variable-value (car vars) *current-frame*)
- ,value-var))))
- (t
- ;; Since we have more than one, first see if we have any
- ;; variables that exactly match the specification.
- (let* ((name (etypecase name
- (symbol (symbol-name name))
- (simple-string name)))
- (exact (remove-if-not #'(lambda (v)
- (string= (di:debug-variable-name v)
- name))
- vars))
- (vars (or exact vars)))
- (declare (simple-string name)
- (list exact vars))
- (cond
- ;; Check now for only having one variable.
- ((= (length vars) 1)
- ,(ecase ref-or-set
- (:ref
- '(di:debug-variable-value (car vars) *current-frame*))
- (:set
- `(setf (di:debug-variable-value (car vars) *current-frame*)
- ,value-var))))
- ;; If there weren't any exact matches, flame about ambiguity
- ;; unless all the variables have the same name.
- ((and (not exact)
- (find-if-not
- #'(lambda (v)
- (string= (di:debug-variable-name v)
- (di:debug-variable-name (car vars))))
- (cdr vars)))
- (error "Specification ambiguous:~%~{ ~A~%~}"
- (mapcar #'di:debug-variable-name
- (delete-duplicates
- vars :test #'string=
- :key #'di:debug-variable-name))))
- ;; All names are the same, so see if the user ID'ed one of them.
- (id-supplied
- (let ((v (find id vars :key #'di:debug-variable-id)))
- (unless v
- (error "Invalid variable ID, ~D, should have been one of ~S."
- id (mapcar #'di:debug-variable-id vars)))
- ,(ecase ref-or-set
- (:ref
- '(di:debug-variable-value v *current-frame*))
- (:set
- `(setf (di:debug-variable-value v *current-frame*)
- ,value-var)))))
- (t
- (error "Specify variable ID to disambiguate ~S. Use one of ~S."
- name (mapcar #'di:debug-variable-id vars)))))))))
-
- ) ;EVAL-WHEN
-
- ;;; VAR -- Public.
- ;;;
- (defun var (name &optional (id 0 id-supplied))
- "Returns a variable's value if possible. Name is a simple-string or symbol.
- If it is a simple-string, it is an initial substring of the variable's name.
- If name is a symbol, it has the same name and package as the variable whose
- value this function returns. If the symbol is uninterned, then the variable
- has the same name as the symbol, but it has no package.
-
- If name is the initial substring of variables with different names, then
- this return no values after displaying the ambiguous names. If name
- determines multiple variables with the same name, then you must use the
- optional id argument to specify which one you want. If you left id
- unspecified, then this returns no values after displaying the distinguishing
- id values.
-
- The result of this function is limited to the availability of variable
- information. This is SETF'able."
- (define-var-operation :ref))
- ;;;
- (defun (setf var) (value name &optional (id 0 id-supplied))
- (define-var-operation :set value))
-
-
-
- ;;; ARG -- Public.
- ;;;
- (defun arg (n)
- "Returns the n'th argument's value if possible. Argument zero is the first
- argument in a frame's default printed representation. Count keyword/value
- pairs as separate arguments."
- (multiple-value-bind
- (var lambda-var-p)
- (nth-arg n (handler-case (di:debug-function-lambda-list
- (di:frame-debug-function *current-frame*))
- (di:lambda-list-unavailable ()
- (error "No argument values are available."))))
- (if lambda-var-p
- (lambda-var-dispatch var (di:frame-code-location *current-frame*)
- (error "Unused arguments have no values.")
- (di:debug-variable-value var *current-frame*)
- (error "Invalid argument value."))
- var)))
-
- ;;; NTH-ARG -- Internal.
- ;;;
- ;;; This returns the n'th arg as the user sees it from args, the result of
- ;;; DI:DEBUG-FUNCTION-LAMBDA-LIST. If this returns a potential debug-variable
- ;;; from the lambda-list, then the second value is t. If this returns a
- ;;; keyword symbol or a value from a rest arg, then the second value is nil.
- ;;;
- (defun nth-arg (count args)
- (let ((n count))
- (dolist (ele args (error "Argument specification out of range -- ~S." n))
- (lambda-list-element-dispatch ele
- :required ((if (zerop n) (return (values ele t))))
- :optional ((if (zerop n) (return (values (second ele) t))))
- :keyword ((cond ((zerop n)
- (return (values (second ele) nil)))
- ((zerop (decf n))
- (return (values (third ele) t)))))
- :deleted ((if (zerop n) (return (values ele t))))
- :rest ((let ((var (second ele)))
- (lambda-var-dispatch var
- (di:frame-code-location *current-frame*)
- (error "Unused rest-arg before n'th argument.")
- (dolist (value
- (di:debug-variable-value var *current-frame*)
- (error "Argument specification out of range -- ~S."
- n))
- (if (zerop n)
- (return-from nth-arg (values value nil))
- (decf n)))
- (error "Invalid rest-arg before n'th argument.")))))
- (decf n))))
-
-
-
- ;;;; Debug loop commands.
-
- (defvar *debug-commands* nil)
-
- ;;; DEF-DEBUG-COMMAND -- Internal.
- ;;;
- ;;; Interface to *debug-commands*. No required arguments in args are
- ;;; permitted.
- ;;;
- (defmacro def-debug-command (name args &rest body)
- (let ((fun-name (intern (concatenate 'simple-string name "-DEBUG-COMMAND"))))
- `(progn
- (when (assoc ,name *debug-commands* :test #'string=)
- (warn "Redefining ~S debugger command." ,name)
- (setf *debug-commands*
- (remove ,name *debug-commands* :key #'car :test #'string=)))
- (defun ,fun-name ,args
- (unless *in-the-debugger*
- (error "Invoking debugger command while outside the debugger."))
- ,@body)
- (push (cons ,name #',fun-name) *debug-commands*)
- ',fun-name)))
-
- ;;; DEF-DEBUG-COMMAND-ALIAS -- Internal.
- ;;;
- (defun def-debug-command-alias (new-name existing-name)
- (let ((pair (assoc existing-name *debug-commands* :test #'string=)))
- (unless pair (error "Unknown debug command name -- ~S" existing-name))
- (push (cons new-name (cdr pair)) *debug-commands*))
- new-name)
-
- ;;; DEBUG-COMMAND-P -- Internal.
- ;;;
- ;;; This takes a symbol and uses its name to find a debugger command, using
- ;;; initial substring matching. It returns the command function if form
- ;;; identifies only one command, but if form is ambiguous, this returns a list
- ;;; of the command names. If there are no matches, this returns nil. Whenever
- ;;; the loop that looks for a set of possibilities encounters an exact name
- ;;; match, we return that command function immediately.
- ;;;
- (defun debug-command-p (form &optional other-commands)
- (if (or (symbolp form) (integerp form))
- (let* ((name
- (if (symbolp form)
- (symbol-name form)
- (format nil "~d" form)))
- (len (length name))
- (res nil))
- (declare (simple-string name)
- (fixnum len)
- (list res))
- ;;
- ;; Find matching commands, punting if exact match.
- (flet ((match-command (ele)
- (let* ((str (car ele))
- (str-len (length str)))
- (declare (simple-string str)
- (fixnum str-len))
- (cond ((< str-len len))
- ((= str-len len)
- (when (string= name str :end1 len :end2 len)
- (return-from debug-command-p (cdr ele))))
- ((string= name str :end1 len :end2 len)
- (push ele res))))))
- (mapc #'match-command *debug-commands*)
- (mapc #'match-command other-commands))
- ;;
- ;; Return the right value.
- (cond ((not res) nil)
- ((= (length res) 1)
- (cdar res))
- (t ;Just return the names.
- (do ((cmds res (cdr cmds)))
- ((not cmds) res)
- (setf (car cmds) (caar cmds))))))))
-
-
- ;;;
- ;;; Returns a list of debug commands (in the same format as *debug-commands*)
- ;;; that invoke each active restart.
- ;;;
- ;;; Two commands are made for each restart: one for the number, and one for
- ;;; the restart name (unless it's been shadowed by an earlier restart of the
- ;;; same name.
- ;;;
- (defun make-restart-commands (&optional (restarts *debug-restarts*))
- (let ((commands)
- (num 0)) ; better be the same as show-restarts!
- (dolist (restart restarts)
- (let ((name (string (restart-name restart))))
- (unless (find name commands :key #'car :test #'string=)
- (let ((restart-fun
- #'(lambda ()
- (invoke-restart-interactively restart))))
- (push (cons name restart-fun) commands)
- (push (cons (format nil "~d" num) restart-fun) commands))))
- (incf num))
- commands))
-
- ;;;
- ;;; Frame changing commands.
- ;;;
-
- (def-debug-command "UP" ()
- (let ((next (di:frame-up *current-frame*)))
- (cond (next
- (setf *current-code-location* nil)
- (setf *current-frame* next)
- (print-frame-call next))
- (t
- (format t "~&Top of stack.")))))
-
- (def-debug-command "DOWN" ()
- (let ((next (di:frame-down *current-frame*)))
- (cond (next
- (setf *current-code-location* nil)
- (setf *current-frame* next)
- (print-frame-call next))
- (t
- (format t "~&Bottom of stack.")))))
-
- (def-debug-command-alias "D" "DOWN")
-
- (def-debug-command "TOP" ()
- (do ((prev *current-frame* lead)
- (lead (di:frame-up *current-frame*) (di:frame-up lead)))
- ((null lead)
- (setf *current-code-location* nil)
- (setf *current-frame* prev)
- (print-frame-call prev))))
-
- (def-debug-command "BOTTOM" ()
- (do ((prev *current-frame* lead)
- (lead (di:frame-down *current-frame*) (di:frame-down lead)))
- ((null lead)
- (setf *current-code-location* nil)
- (setf *current-frame* prev)
- (print-frame-call prev))))
-
-
- (def-debug-command-alias "B" "BOTTOM")
-
- (def-debug-command "FRAME" (&optional
- (n (read-prompting-maybe "Frame number: ")))
- (let ((current (di:frame-number *current-frame*)))
- (setf *current-code-location* nil)
- (cond ((= n current)
- (princ "You are here."))
- ((> n current)
- (print-frame-call
- (setf *current-frame*
- (do ((prev *current-frame* lead)
- (lead (di:frame-down *current-frame*)
- (di:frame-down lead)))
- ((null lead)
- (princ "Bottom of stack encountered.")
- prev)
- (when (= n (di:frame-number prev))
- (return prev))))))
- (t
- (print-frame-call
- (setf *current-frame*
- (do ((prev *current-frame* lead)
- (lead (di:frame-up *current-frame*)
- (di:frame-up lead)))
- ((null lead)
- (princ "Top of stack encountered.")
- prev)
- (when (= n (di:frame-number prev))
- (return prev)))))))))
-
- (def-debug-command-alias "F" "FRAME")
-
- ;;;
- ;;; In and Out commands.
- ;;;
-
- (def-debug-command "QUIT" ()
- (throw 'lisp::top-level-catcher nil))
-
- (def-debug-command "GO" ()
- (continue)
- (error "No restart named continue."))
-
- (def-debug-command "RESTART" ()
- (let ((num (read-if-available :prompt)))
- (when (eq num :prompt)
- (show-restarts *debug-restarts*)
- (write-string "Restart: ")
- (force-output)
- (setf num (read *standard-input*)))
- (let ((restart (typecase num
- (unsigned-byte
- (nth num *debug-restarts*))
- (symbol
- (find num *debug-restarts* :key #'restart-name
- :test #'(lambda (sym1 sym2)
- (string= (symbol-name sym1)
- (symbol-name sym2)))))
- (t
- (format t "~S is invalid as a restart name.~%" num)
- (return-from restart-debug-command nil)))))
- (if restart
- (invoke-restart-interactively restart)
- (princ "No such restart.")))))
-
- ;;;
- ;;; Information commands.
- ;;;
-
- (defvar *help-line-scroll-count* 20
- "This controls how many lines the debugger's help command prints before
- printing a prompting line to continue with output.")
-
- (def-debug-command "HELP" ()
- (let* ((end -1)
- (len (length debug-help-string))
- (len-1 (1- len)))
- (loop
- (let ((start (1+ end))
- (count *help-line-scroll-count*))
- (loop
- (setf end (position #\newline debug-help-string :start (1+ end)))
- (cond ((or (not end) (= end len-1))
- (setf end len)
- (return))
- ((or (zerop (decf count)) (= end len))
- (return))))
- (write-string debug-help-string *standard-output*
- :start start :end end))
- (when (= end len) (return))
- (format t "~%[RETURN FOR MORE, Q TO QUIT HELP TEXT]: ")
- (force-output)
- (let ((res (read-line)))
- (when (or (string= res "q") (string= res "Q"))
- (return))))))
-
- (def-debug-command-alias "?" "HELP")
-
- (def-debug-command "ERROR" ()
- (format t "~A~%" *debug-condition*)
- (show-restarts *debug-restarts*))
-
- (def-debug-command "BACKTRACE" ()
- (backtrace (read-if-available most-positive-fixnum)))
-
- (def-debug-command "PRINT" ()
- (print-frame-call *current-frame*))
-
- (def-debug-command-alias "P" "PRINT")
-
- (def-debug-command "VPRINT" ()
- (print-frame-call *current-frame* nil nil))
-
- (def-debug-command-alias "PP" "VPRINT")
-
- (def-debug-command "LIST-LOCALS" ()
- (let ((d-fun (di:frame-debug-function *current-frame*)))
- (if (di:debug-variable-info-available d-fun)
- (let ((*print-level* (or *debug-print-level* *print-level*))
- (*print-length* (or *debug-print-length* *print-level*))
- (*standard-output* *debug-io*)
- (location (di:frame-code-location *current-frame*))
- (prefix (read-if-available nil))
- (any-p nil)
- (any-valid-p nil))
- (dolist (v (di:ambiguous-debug-variables
- d-fun
- (if prefix (string prefix) "")))
- (setf any-p t)
- (when (eq (di:debug-variable-validity v location) :valid)
- (setf any-valid-p t)
- (format t "~A~:[#~D~;~*~] = ~S~%"
- (di:debug-variable-name v)
- (zerop (di:debug-variable-id v))
- (di:debug-variable-id v)
- (di:debug-variable-value v *current-frame*))))
-
- (cond
- ((not any-p)
- (format t "No local variables ~@[starting with ~A ~]~
- in function."
- prefix))
- ((not any-valid-p)
- (format t "All variables ~@[starting with ~A ~]currently ~
- have invalid values."
- prefix))))
- (write-line "No variable information available."))))
-
- (def-debug-command "SOURCE" ()
- (print-code-location-source-form (or *current-code-location*
- (di:frame-code-location
- *current-frame*))
- (read-if-available 0)))
-
- (def-debug-command "VSOURCE" ()
- (print-code-location-source-form (or *current-code-location*
- (di:frame-code-location
- *current-frame*))
- (read-if-available 0)
- t))
-
- ;;; PRINT-CODE-LOCATION-SOURCE-FORM -- Internal.
- ;;;
- (defun print-code-location-source-form (code-loc context &optional verbose)
- (let* ((location (maybe-block-start-location code-loc))
- (d-source (di:code-location-debug-source location))
- (name (di:debug-source-name d-source)))
- (ecase (di:debug-source-from d-source)
- (:file
- (print-frame-source-from-file context verbose location d-source name))
- ((:lisp :stream)
- (let ((tlf (di:code-location-top-level-form-offset location)))
- (print-frame-source
- (svref name tlf)
- tlf
- location context verbose))))))
-
- ;;; MAYBE-BLOCK-START-LOCATION -- Internal.
- ;;;
- ;;; If Loc is an unknown location, then try to find the block start location.
- ;;; PRINT-FRAME-SOURCE-FORM uses this to show some source form; since loc
- ;;; is unknown, we provide some information instead of none for the user.
- ;;;
- (defun maybe-block-start-location (loc)
- (if (di:code-location-unknown-p loc)
- (let* ((block (di:code-location-debug-block loc))
- (start (di:do-debug-block-locations (loc block)
- (return loc))))
- (cond ((and (not (di:debug-block-elsewhere-p block))
- start)
- (format t "~%Unknown location: using block start.~%")
- start)
- (t
- loc)))
- loc))
-
- ;;; PRINT-FRAME-SOURCE-FROM-FILE -- Internal.
- ;;;
- ;;; This does the work of PRINT-FRAME-SOURCE-FORM when the source is a file.
- ;;; It takes care of the source no longer existing or someone having modified
- ;;; it since the system read the source.
- ;;;
- (defun print-frame-source-from-file (context verbose location d-source name)
- (cond ((not (probe-file name))
- (format t "~%Source file no longer exists:~% ~A."
- (namestring name)))
- (t
- (let* ((tlf-offset (di:code-location-top-level-form-offset location))
- (local-tlf-offset (- tlf-offset
- (di:debug-source-root-number d-source)))
- (char-offset (aref (or (di:debug-source-start-positions d-source)
- (error "No start positions map."))
- local-tlf-offset)))
- (with-open-file (f name)
- (cond
- ((= (di:debug-source-created d-source)
- (file-write-date name))
- (file-position f char-offset))
- (t
- (format t "~%File has been modified since compilation:~% ~A~@
- Using form offset instead of character position.~%"
- (namestring name))
- (let ((*read-suppress* t))
- (dotimes (i local-tlf-offset)
- (read f)))))
- (format t "~%File: ~A~%" (namestring name))
- (print-frame-source (read f) tlf-offset location context
- verbose))))))
-
- ;;; PRINT-FRAME-SOURCE -- Internal.
- ;;;
- (defun print-frame-source (tlf tlf-offset location context verbose)
- (let ((*print-level* (if verbose
- nil
- (or *debug-print-level* *print-level*)))
- (*print-length* (if verbose
- nil
- (or *debug-print-length* *print-length*))))
- (print (di:source-path-context
- tlf
- (svref (di:form-number-translations tlf tlf-offset)
- (di:code-location-form-number location))
- context))))
-
- ;;;
- ;;; Miscellaneous commands.
- ;;;
-
- (def-debug-command "FLUSH-ERRORS" ()
- (if (setf *flush-debug-errors* (not *flush-debug-errors*))
- (write-line "Errors now flushed.")
- (write-line "Errors now create nested debug levels.")))
-
-
- (def-debug-command "DESCRIBE" ()
- (let* ((curloc (or *current-code-location*
- (di:frame-code-location *current-frame*)))
- (debug-fun (di:code-location-debug-function curloc))
- (function (di:debug-function-function debug-fun)))
- (if function
- (describe function)
- (format t "Can't figure out the function for this frame."))))
-
-
- ;;;
- ;;; Editor commands.
- ;;;
-
- (def-debug-command "EDIT-SOURCE" ()
- (unless (typep *terminal-io* 'ed::ts-stream)
- (error "The debugger's EDIT-SOURCE command only works in slave Lisps ~
- connected to a Hemlock editor."))
- (let* ((wire (ed::ts-stream-wire *terminal-io*))
- (location (maybe-block-start-location
- (or *current-code-location*
- (di:frame-code-location *current-frame*))))
- (d-source (di:code-location-debug-source location))
- (name (di:debug-source-name d-source)))
- (ecase (di:debug-source-from d-source)
- (:file
- (let* ((tlf-offset (di:code-location-top-level-form-offset location))
- (local-tlf-offset (- tlf-offset
- (di:debug-source-root-number d-source)))
- (char-offset (aref (or (di:debug-source-start-positions d-source)
- (error "No start positions map."))
- local-tlf-offset)))
- (wire:remote wire
- (ed::edit-source-location (namestring name)
- (di:debug-source-created d-source)
- tlf-offset local-tlf-offset char-offset
- (di:code-location-form-number location)))
- (wire:wire-force-output wire)))
- ((:lisp :stream)
- (wire:remote wire
- (ed::cannot-edit-source-location))
- (wire:wire-force-output wire)))))
-
-
-
- ;;;; Debug loop command utilities.
-
- (defun read-prompting-maybe (prompt &optional (in *standard-input*)
- (out *standard-output*))
- (unless (ext:listen-skip-whitespace in)
- (princ prompt out)
- (force-output out))
- (read in))
-
- (defun read-if-available (default &optional (stream *standard-input*))
- (if (ext:listen-skip-whitespace stream)
- (read stream)
- default))
-