;;; debug.lisp ;;; ;;; Copyright (C) 2003-2007 Peter Graves ;;; $Id$ ;;; ;;; This program is free software; you can redistribute it and/or ;;; modify it under the terms of the GNU General Public License ;;; as published by the Free Software Foundation; either version 2 ;;; of the License, or (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;;; ;;; As a special exception, the copyright holders of this library give you ;;; permission to link this library with independent modules to produce an ;;; executable, regardless of the license terms of these independent ;;; modules, and to copy and distribute the resulting executable under ;;; terms of your choice, provided that you also meet, for each linked ;;; independent module, the terms and conditions of the license of that ;;; module. An independent module is a module which is not derived from ;;; or based on this library. If you modify this library, you may extend ;;; this exception to your version of the library, but you are not ;;; obligated to do so. If you do not wish to do so, delete this ;;; exception statement from your version. ;;; Adapted from SBCL. (in-package #:extensions) (export '(*debug-condition* *debug-level* show-restarts)) (defvar *debug-condition* nil) (defvar *debug-level* 0) (in-package #:system) (defun show-restarts (restarts stream) (when restarts (fresh-line stream) (%format stream "Restarts:~%") (let ((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)))))) (let ((count 0)) (dolist (restart restarts) (let ((name (restart-name restart)) (report-function (restart-report-function restart))) (%format stream " ~D: ~A" count name) (when (functionp report-function) (dotimes (i (1+ (- max-name-len (length (princ-to-string name))))) (write-char #\space stream)) (funcall report-function stream)) (terpri stream)) (incf count)))))) (defun internal-debug () (if (fboundp 'tpl::repl) (let* ((current-debug-io (if (typep *debug-io* 'synonym-stream) (symbol-value (synonym-stream-symbol *debug-io*)) *debug-io*)) (in (two-way-stream-input-stream current-debug-io)) (out (two-way-stream-output-stream current-debug-io))) (loop (tpl::repl in out))) (quit))) (defun debug-loop () (let ((*debug-level* (1+ *debug-level*))) (show-restarts (compute-restarts) *debug-io*) (internal-debug))) (defun invoke-debugger-report-condition (condition) (when condition (fresh-line *debug-io*) (with-standard-io-syntax (let ((*print-structure* nil) (*print-readably* nil)) (when (and *load-truename* (streamp *load-stream*)) (simple-format *debug-io* "Error loading ~A at line ~D (offset ~D)~%" *load-truename* (stream-line-number *load-stream*) (stream-offset *load-stream*))) (simple-format *debug-io* (if (fboundp 'tpl::repl) "~S: Debugger invoked on condition of type ~A~%" "~S: Unhandled condition of type ~A:~%") (threads:current-thread) (type-of condition)) (simple-format *debug-io* " ~A~%" condition))))) (declaim (inline run-hook)) (defun run-hook (hook &rest args) (let ((hook-function (symbol-value hook))) (when hook-function (progv (list hook) (list nil) (apply hook-function args))))) (defvar *invoke-debugger-hook* nil "Like *DEBUGGER-HOOK* but observed by INVOKE-DEBUGGER even when called by BREAK. This hook is run before *DEBUGGER-HOOK*.") ;;; We run *INVOKE-DEBUGGER-HOOK* before *DEBUGGER-HOOK* because SBCL ;;; does so, too, and for good reason: This way, you can specify ;;; default debugger behaviour that trumps over whatever the users ;;; wants to do with *DEBUGGER-HOOK*. (defun invoke-debugger (condition) (let ((*saved-backtrace* (sys:backtrace))) (run-hook '*invoke-debugger-hook* condition *invoke-debugger-hook*) (run-hook '*debugger-hook* condition *debugger-hook*) (invoke-debugger-report-condition condition) (unless (fboundp 'tpl::repl) (quit)) (let ((original-package *package*)) (with-standard-io-syntax (let ((*package* original-package) (*print-readably* nil) ; Top-level default. (*print-structure* nil) (*debug-condition* condition) (level *debug-level*)) (clear-input *debug-io*) (if (> level 0) (with-simple-restart (abort "Return to debug level ~D." level) (debug-loop)) (debug-loop))))))) (defun break (&optional (format-control "BREAK called") &rest format-arguments) (let ((*debugger-hook* nil)) ; Specifically required by ANSI. (with-simple-restart (continue "Return from BREAK.") (invoke-debugger (%make-condition 'simple-condition (list :format-control format-control :format-arguments format-arguments)))) nil)) (defun backtrace-as-list (&optional (n 0)) "Return BACKTRACE with each element converted to a list." (mapcar #'sys::frame-to-list (sys:backtrace n)))