;;; -*- Mode:LISP; Package:EH; Lowercase:T; Base:10; Readtable:ZL -*- ;;;; Stepping commands. ;; Control-X: Control the trap-on-exit bits of frames. (defun com-toggle-frame-trap-on-exit (sg ignore &optional ignore) "Toggles whether we trap on exit from this frame." (let ((trap-p (not (trap-on-exit-p sg *current-frame*)))) (set-trap-on-exit sg *current-frame* trap-p) (terpri) (princ (if (trap-on-exit-p sg *current-frame*) "Break" "Do not break")) (princ " on exit from this frame."))) (defun set-trap-on-exit (sg frame trap-p) "Set or clear trap on exit from FRAME in SG. TRAP-P = T means set, else clear." (let ((rp (sg-regular-pdl sg))) (if (eq (rp-function-word rp frame) #'*catch) (setq trap-p nil)) (setf (si::rp-attention rp frame) 1) (setf (rp-trap-on-exit rp frame) (if trap-p 1 0))) trap-p) (defun trap-on-exit-p (sg frame) "T if FRAME in SG is set to trap on being exited." (not (zerop (rp-trap-on-exit (sg-regular-pdl sg) frame)))) ;; Meta-X (defun com-set-all-frames-trap-on-exit (sg ignore &optional ignore) "Makes all outer frames trap on exit." (do ((frame *current-frame* (sg-next-active sg frame))) ((null frame)) (set-trap-on-exit sg frame t)) (format t "~%Break on exit from this frame and all outer active frames.")) ;; Control-Meta-X (defun com-clear-all-frames-trap-on-exit (sg ignore &optional ignore) "Clears the trap-on-exit flag for all outer frames." (do ((frame *current-frame* (sg-next-open sg frame))) ((null frame)) (set-trap-on-exit sg frame nil)) (format t "~%Do not break on exit from this frame and all outer frames.")) ;; Control-D: Proceed, and trap next function call. (defun com-proceed-trap-on-call (sg error-object &optional ignore) "Proceeds from this error (if that is possible) and traps on the next function call." (setf (sg-flags-trap-on-call sg) 1) (format t "Trap on next function call. ") (com-proceed sg error-object)) ;; Meta-D (defun com-toggle-trap-on-call (sg ignore &optional ignore) "Toggle whether to trap on next function call." (setf (sg-flags-trap-on-call sg) (logxor 1 (sg-flags-trap-on-call sg))) (terpri) (princ (if (zerop (sg-flags-trap-on-call sg)) "Do not break" "Break")) (princ " on next function call.")) ;;;; Commands for single-stepping and setting breakpoints ;; Meta-Shift-s ;>> SHOULD DO SOMETHING WITH ARG (like step n steps, not single-step called function, etc) (defun com-macro-single-step (sg error-object &optional arg) "Single steps macroinstructions. Should do something useful with an arg, but doesn't. Foo." (declare (special error-object) (ignore arg)) (if (not *error-handler-running*) (throw 'exit t)) ;>> Gak! (cond ((and (send error-object :debugging-condition-p) (send error-object :proceed-asking-user :operation-handled-p :no-action)) (setf (getf (sg-plist sg) 'single-macro-dispatch) t) (format t "Single step...") (proceed-error-sg :no-action) nil) (t (format t "Cannot single-step from an error")))) ;; Control-Shift-s (defun com-set-breakpoint (sg ignore &optional arg) "Sets a breakpoint in a compiled function. With a numeric arg, sets a breakpoint at that pc withing the current function. Otherwise prompts for a function and pc." (let ((function (rp-function-word (sg-regular-pdl sg) *current-frame*)) pc) (if arg (setq pc arg) (multiple-value-setq (function pc) (get-compiled-function-and-pc function))) (let ((loss (multiple-value-list (check-for-bogus-pc function pc)))) (if (null loss) (set-breakpoint function pc t) (apply #'format t loss))))) ;; Control-Shift-c (defun com-clear-breakpoint (sg ignore &optional arg) "Clears a brekpoint from a compiled function. With no argument, prompts for a function and pc. With an arg of -1, clears all breakpoints from the current function With any other argument, clears a breakpoint at that pc of the current function" (format t " Clear macrocode breakpoint") (let ((function (rp-function-word (sg-regular-pdl sg) *current-frame*)) pc) (cond ((eq arg -1) (format t "~P from ~S" (length (function-breakpoints function)) function) (clear-breakpoint function nil t) (return-from com-clear-breakpoint nil)) (arg (format t " at ~D" arg) (setq pc arg)) (t (multiple-value-setq (function pc) (get-compiled-function-and-pc function)))) (let ((loss (multiple-value-list (check-for-bogus-pc function pc)))) (if (null loss) (clear-breakpoint function pc t) (apply #'format t loss))))) ;; Meta-Shift-c (defun com-list-breakpoints (ignore ignore &optional arg) "Lists all functions which have breakpoints set in them and the pc's at which they have breakpoints" (declare (ignore arg)) (dolist (f *fefs-with-breakpoints*) (let ((breakpoints (function-breakpoints f))) (format t "~&Function ~S has ~:[a breakpoint~;breakpoints~] at pc~:*~[~;~'s~] ~{~D~^, ~}" (cdr breakpoints) breakpoints)))) ;;>> (defun com-clear-all-breakpoints (ignore ignore &optional arg) "Clears all breakpoints from all functions which have them" (declare (ignore arg)) (dolist (f *fefs-with-breakpoints*) (clear-breakpoint f nil t))) (defun check-for-bogus-pc (fef pc) (check-type fef compiled-function) (let ((name (fef-name fef)) (lim (fef-limit-pc fef)) (min (fef-initial-pc fef))) (if (not (fixnump pc)) (values "The pc must be a fixnum!") (cond ((< pc min) (values "A pc of ~D is invalid for function ~S, whose instructions start at pc ~D" pc name min)) ((> pc lim) (values "A pc of ~D is invalid for function ~S (which has ~D instructions)" pc name lim)) (t (do (len (n min)) ((> n lim) (values)) (setq len (fef-instruction-length fef n)) (cond ((= n pc) (return (values))) ((< n pc (setq n (+ n len))) (return-from check-for-bogus-pc (values "~The pc ~D lies in the middle of a multi-word instruction in ~S (the instruction starts at ~D)~" pc name (- n len))))))))))) (defun get-compiled-function-and-pc (&optional default-function) (declare (values function pc)) (let* ((function-name (and default-function (function-name default-function))) (prompt #'(lambda (stream ignore) (format stream "~&Function name~@[, or ~*~C for default (~S)~]: " function-name #/end function-name))) function) (setq function (with-input-editing (*debug-io* `((:prompt ,prompt) (:activation char= #/end) ;; :full-rubout is too obnoxious )) (prog () loop (multiple-value-bind (fn flag) (si:read-or-end *debug-io* nil nil) (if (eq flag ':end) (setq fn default-function)) (unless (compiled-function-p fn) (condition-case (error) (setq fn (fdefinition fn)) (error (parse-ferror "~A" error) (go loop))) (unless (compiled-function-p fn) (parse-ferror "~S is not a compiled function" fn) (go loop))) (return fn)))) function-name (function-name function)) (let ((min (fef-initial-pc function)) (lim (fef-limit-pc function))) (setq prompt #'(lambda (stream ignore) (format stream "~&PC within function (between ~D and ~D): " min lim))) (with-input-editing (*debug-io* `((:prompt ,prompt) ;character lossage (:activation memq (#.(char-int #/end) #.(char-int #/newline))) (:no-input-save t))) (tagbody loop (let* ((*read-base* 10.) (pc (si:read-for-top-level *debug-io* nil nil)) (loss (multiple-value-list (check-for-bogus-pc function pc)))) (if (null loss) (return-from get-compiled-function-and-pc (values function pc)) (apply #'parse-ferror loss) (go loop)))))))) ;;;; The guts of breakpointing (defvar *fefs-with-breakpoints* () "List of fef's which contain a breakpoint") (defun function-breakpoints (function) "Returns a list ((pc original-instruction-code) ...)" (cdr (assq 'breakpoints (debugging-info function)))) (defun set-breakpoint (function pc &optional print) "Sets a breakpoint at PC in function FUNCTION (which must be a compiled function) PRINT means to print a message on *DEBUG-IO* saying that the breakpoint has been set. Returns T if successful." (check-type function compiled-function) (cond ((not ( (fef-initial-pc function) pc (fef-limit-pc function))) (ferror "~D is not a valid pc in ~S" pc function)) ((not (fef-debugging-info-present-p function)) (ferror "~S doesn't have a debugging-info slot. You lose" function))) (without-interrupts (let* ((debugging-info (fef-debugging-info function)) (breakpoints (assq 'breakpoints debugging-info)) (default-cons-area background-cons-area) (bpt compiler::(lap-word-eval '(misc bpt d-ignore))) (function-name (function-name function))) (if (and (assq pc (cdr breakpoints)) (eq (fef-instruction function pc) bpt)) (when print (format *debug-io* "~&Breakpoint already exists at pc ~D in ~S" pc function-name)) (let ((inst (fef-instruction function pc))) (let ((%inhibit-read-only t)) (if breakpoints (push (list pc inst) (cdr breakpoints)) (push `(breakpoints . ((,pc ,inst))) (fef-debugging-info function))) (setf (fef-instruction function pc) bpt))) (pushnew function *fefs-with-breakpoints* :test #'eq) (when print (format *debug-io* "~&Breakpoint set at pc ~D in ~S" pc function-name)) t)))) (defun clear-breakpoint (function &optional pc print) "Clears a breakpoint at PC in function FUNCTION (which must be a compiled function) If PC is not supplied, then clears all breakpoints in FUNCTION PRINT means to print a message in *DEBUG-IO* saying that the breakpoint has been cleared." (check-type function compiled-function) (without-interrupts (let* ((debugging-info (and (fef-debugging-info-present-p function) (fef-debugging-info function))) (breakpoints (assq 'breakpoints debugging-info)) (bpt compiler::(lap-word-eval '(misc bpt d-ignore))) (function-name (function-name function))) (flet ((do-it (pc &aux (bp (assq pc (cdr breakpoints)))) (cond ((not ( (fef-initial-pc function) pc (fef-limit-pc function))) (with-stack-list (args "~D is not a valid pc in ~S" pc function) (if print (apply #'format *debug-io* args) (apply #'ferror args))) nil) ((not (eq (fef-instruction function pc) bpt)) (with-stack-list (args (if (cdr breakpoints) "~&There is no breakpoint in ~S at pc ~D" "~&There are no breakpoints set in ~S") function pc) (if print (apply #'format *debug-io* args) (apply #'ferror args))) nil) ((null bp) (ferror "~There is a breakpoint in ~S at pc ~D. However, the information necessary to remove it has been lost! You lose big!!" function pc)) (t (let ((%inhibit-read-only t)) (setf (fef-instruction function pc) (cadr bp)) (setf (cdr breakpoints) (delq bp (cdr breakpoints)))) t)))) (if pc (and (do-it pc) print (format *debug-io* "~&Breakpoint cleared at pc ~D in ~S" pc function-name)) (let ((winners)) (dolist (c (cdr breakpoints)) (and (do-it (car c)) print (push (car c) winners))) (when print (if (null winners) (format *debug-io* "~&No breakpoints in ~S" function) (format *debug-io* "~&Breakpoint~@[s~] cleared from ~S at pc~@['s~] ~{~D~^ ~}" (eq (length winners) 1) winners))))) (if (null breakpoints) (setq *fefs-with-breakpoints* (delq function *fefs-with-breakpoints*))))))) ;;;; the real guts of breakpointing ;; the real hard stuff ;; the stuff I haven't written ;>> ;(defmethod (breakpoint-error :case :proceed-asking-user :no-action) () ; ) ;(defmethod (breakpoint-error :case :proced-asking-user :single-step) () ; ) ;(defun proceed-breakpoint (sg fef pc single-step-p) ; (let* ((bpt (assq pc (assq 'breakpoints (fef-debugging-info fef)))) ; (inst (cadr bpt))) ; (if (null bpt) ; (ferror "Foo! I don't know about a breakpoint in ~S at ~D. Lossage!!" fef pc) ; (without-interrupts ; (let ((%inhibit-read-only t)) ; (swapf (fef-instruction fef pc) inst))) ; (setf (getf (sg-plist sg) 'single-macro-dispatch) t) ; (proceed-error-sg :no-action) ;;;; BREAKON (defvar *breakon-function-specs* () "List of all function-specs that have BREAKONs.") (defun breakon (&optional function-spec (condition t)) "Break on entry to FUNCTION-SPEC, if CONDITION evaluates non-NIL. If called repeatedly for one function-spec with different conditions, a break will happen if any of the conditions evaluates non-NIL. With no args, returns a list of function specs that have had break-on-entry requested with BREAKON." (if (null function-spec) *breakon-function-specs* (setq function-spec (dwimify-arg-package function-spec 'function-spec)) (breakon-init function-spec) (setq condition (si:rename-within-new-definition-maybe function-spec condition)) (let* ((spec1 (si:unencapsulate-function-spec function-spec 'breakon))) (uncompile spec1 t) (let* ((def (fdefinition spec1)) (default-cons-area background-cons-area) ;; Find our BREAKON-THIS-TIME. ;; def looks like: ;; (named-lambda (foo debugging-info) arglist ;; (si::encapsulation-let ((arglist (si::encapsulation-list* arglist))) ;; (declare (special arglist)) ;; (breakon-this-time (or . conditions) unencapsulated-function arglist))) (defn-data (car (si::encapsulation-body def))) (slot-loc (cadr defn-data))) ;Within that, find ptr to list of conditions. (pushnew condition (cdr slot-loc) :test #'equal))) (if compile-encapsulations-flag (compile-encapsulations function-spec 'breakon)) function-spec)) (defun unbreakon (&optional function-spec (condition t)) "Remove break on entry to FUNCTION-SPEC, or all functions if no arg. If CONDITION is specified, we remove only that condition for breaking; if other conditions have been specified with BREAKON on this function, the other conditions remain in effect." (when function-spec (setq function-spec (dwimify-arg-package function-spec 'function-spec))) (let* ((spec1 (and function-spec (si:unencapsulate-function-spec function-spec 'breakon)))) (cond ((null function-spec) (mapc #'unbreakon *breakon-function-specs*)) ((eq condition t) (fdefine spec1 (fdefinition (si:unencapsulate-function-spec spec1 '(breakon)))) (setq *breakon-function-specs* (cl:delete function-spec *breakon-function-specs* :test #'equal)) function-spec) ((neq spec1 (si:unencapsulate-function-spec spec1 '(breakon))) (uncompile spec1 t) (let* ((def (fdefinition spec1)) ;; Find our BREAKON-NEXT-TIME. ;; def looks like: ;; (named-lambda (foo debugging-info) arglist ;; (si::encapsulation-let ((arglist (si::encapsulation-list* arglist))) ;; (declare (special arglist)) ;; (breakon-this-time (or . conditions) unencapsulated-function arglist))) (defn-data (car (si::encapsulation-body def))) (slot-loc (cadr defn-data))) ;Within that, find ptr to list of conditions. (setf (cdr slot-loc) (cl:delete condition (cdr slot-loc) :test #'equal)) (cond ((null (cdr slot-loc)) (fdefine spec1 (fdefinition (si:unencapsulate-function-spec spec1 '(breakon)))) (setq *breakon-function-specs* (cl:delete function-spec *breakon-function-specs* :test #'equal))) (compile-encapsulations-flag (compile-encapsulations function-spec 'breakon)))) function-spec)))) ;;; Make a specifed function into an broken-on function ;;; (with no conditions yet) if it isn't one already. (defun breakon-init (function-spec) (let ((default-cons-area background-cons-area) (spec1 (si:unencapsulate-function-spec function-spec 'breakon))) (when (eq spec1 (si:unencapsulate-function-spec spec1 '(breakon))) (si:encapsulate spec1 function-spec 'breakon ;; Must cons the (OR) afresh -- it gets RPLAC'd. `(breakon-this-time ,(list 'or) ,si::encapsulated-function arglist) '((uninteresting-function debug))) (push function-spec *breakon-function-specs*)))) (defun breakon-this-time (break-condition function args) (declare (uninteresting-function debug)) (when break-condition (select-processor (:cadr (setf (ldb %%m-flags-trap-on-call %mode-flags) 1)) ((:lambda :explorer) (compiler::%trap-on-next-call)))) ;; The next call ought to be the function the user is trying to call. ;; That will be so only if this function is compiled. (apply function args))