;;; DTRACE is a portable alternative to the Common Lisp TRACE and UNTRACE ;;; macros. It offers a more detailed display format than the tracing ;;; tool most Common Lisp implementations provide. ;;; ;;; From the book "Common Lisp: A Gentle Introduction to ;;; Symbolic Computation" by David S. Touretzky. ;;; The Benjamin/Cummings Publishing Co., 1990. ;;; ;;; This version is for Golden Common Lisp version 3.1 and up. ;;; ;;; User-level routines: ;;; DTRACE - same syntax as TRACE ;;; DUNTRACE - same syntax as UNTRACE ;;; ;;; Copyright (c) 1988,1989 Symbolic Technology, Ltd. ;;; This software may not be sold or used for commercial purposes without ;;; prior written permission from the copyright holder. (setq gclisp:*inhibit-system-redefinition-warnings* t) (in-package "DTRACE" :use '("LISP" "GCLISP")) (export '(dtrace::dtrace dtrace::duntrace)) (shadowing-import '(dtrace::dtrace dtrace::duntrace) (find-package "USER")) (use-package "DTRACE" "USER") (defvar *system-packages*) (defconstant *system-package-names* '("LISP" "GCLISP" "SYSTEM" "SYSTEM-INTERNAL" "COMPILER" "GMACS" "EPI" "NREAD" "EXPLORER" "DT" "DBG" "DOS" "BACKEND-336" "BACKEND-286" "DTRACE")) (eval-when (eval load) (setq *system-packages* (mapcar #'find-package *system-package-names*))) (defun systemp (x) (and (symbolp x) (member (symbol-package x) *system-packages*))) ;;; DTRACE and related routines. (defparameter *dtrace-print-length* 7) (defparameter *dtrace-print-level* 4) (defparameter *dtrace-print-circle* t) (defparameter *dtrace-print-pretty* nil) (defparameter *dtrace-print-array* *print-array*) (defvar *traced-functions* nil) (defvar *trace-level* 0) (defmacro dtrace (&rest function-names) "Turns on detailed tracing for specified functions. Undo with DUNTRACE." (if (null function-names) (list 'quote *traced-functions*) (list 'quote (mapcan #'dtrace1 function-names)))) (defun dtrace1 (name) (unless (symbolp name) (format *error-output* "~&~S is an invalid function name." name) (return-from dtrace1 nil)) (unless (fboundp name) (format *error-output* "~&~S undefined function." name) (return-from dtrace1 nil)) (when (special-form-p name) (format *error-output* "~&Can't trace ~S because it's a special form." name) (return-from dtrace1 nil)) (when (systemp name) (format t "~%Warning: ~S is a system function. Tracing certain~%" name) (format t " system functions can lead to unrecoverable errors.~%") (if (y-or-n-p "Are you positive you want to dtrace ~S ? " name) nil (return-from dtrace1 nil))) (eval `(untrace ,name)) ;if they're tracing it, undo their trace (duntrace1 name) ;if we're already tracing it, undo our trace (if (macro-function name) (trace-macro name) (trace-function name)) (setf *traced-functions* (nconc *traced-functions* (list name))) (list name)) ; The functions below reference DISPLAY-... routines that are implementation-specific. ; These routines are defined at the end of the file. (defun trace-function (name) (let* ((formal-arglist (fetch-arglist name)) (old-defn (symbol-function name)) (new-defn #'(lambda (&rest argument-list) (let ((result nil)) (display-function-entry name) (let ((*trace-level* (1+ *trace-level*))) (with-dtrace-printer-settings (show-function-args argument-list formal-arglist)) (setf result (multiple-value-list (apply old-defn argument-list)))) (display-function-return name result) (values-list result))))) (setf (get name 'original-definition) old-defn) (setf (get name 'traced-definition) new-defn) ;in case function was re-DEFUN'ed while traced (setf (get name 'traced-type) 'defun) (setf (symbol-function name) new-defn))) (defun trace-macro (name) (let* ((formal-arglist (fetch-arglist name)) (old-defn (macro-function name)) (new-defn (cons 'macro #'(lambda (macro-args) (let ((result nil)) (display-function-entry name 'macro) (let ((*trace-level* (1+ *trace-level*))) (with-dtrace-printer-settings (show-function-args macro-args formal-arglist)) (setf result (funcall old-defn (cdr macro-args)))) (display-function-return name (list result) 'macro) (values result)))))) (setf (get name 'original-definition) old-defn) (setf (get name 'traced-definition) new-defn) ;in case function was re-DEFUN'ed while traced (setf (get name 'traced-type) 'defmacro) (setf (symbol-function name) new-defn))) (defun show-function-args (actuals formals &optional (argcount 0)) (cond ((null actuals) nil) ((null formals) (handle-args-numerically actuals argcount)) (t (case (first formals) (&optional (show-function-args actuals (rest formals) argcount)) (&rest (show-function-args (list actuals) (rest formals) argcount)) (&key (handle-keyword-args actuals)) (&aux (show-function-args actuals nil argcount)) (t (handle-one-arg (first actuals) (first formals)) (show-function-args (rest actuals) (rest formals) (1+ argcount))))))) (defun handle-args-numerically (actuals argcount) (dolist (x actuals) (incf argcount) (display-arg-numeric x argcount))) (defun handle-one-arg (val varspec) (cond ((atom varspec) (display-one-arg val varspec)) (t (display-one-arg val (first varspec)) (if (third varspec) (display-one-arg t (third varspec)))))) (defun handle-keyword-args (actuals) (cond ((null actuals)) ((keywordp (first actuals)) (display-one-arg (second actuals) (first actuals)) (handle-keyword-args (rest (rest actuals)))) (t (display-one-arg actuals "Extra args:")))) ;;; DUNTRACE and related routines. (defmacro duntrace (&rest function-names) "Turns off tracing for specified functions. With no args, turns off all tracing." (setf *trace-level* 0) ;safety precaution in case things got broken (list 'quote (mapcan #'duntrace1 (or function-names *traced-functions*)))) (defun duntrace1 (name) (unless (symbolp name) (format *error-output* "~&~S is an invalid function name." name) (return-from duntrace1 nil)) (setf *traced-functions* (delete name *traced-functions*)) (let ((orig-defn (get name 'original-definition 'none)) (traced-defn (get name 'traced-definition)) (traced-type (get name 'traced-type 'none))) (unless (or (eq orig-defn 'none) (not (fboundp name)) (not (equal traced-defn (case traced-type (defun (symbol-function name)) (defmacro (symbol-function name)))))) ;in case redefined (case traced-type (defun (setf (symbol-function name) orig-defn)) (defmacro (setf (symbol-function name) orig-defn))))) (remprop name 'traced-definition) (remprop name 'traced-type) (remprop name 'original-definition) (list name)) ;;; Display routines. ; The code below generates vanialla character output for ordinary terminals. ; It can be replaced with special graphics code if the implementation permits, ; e.g., on a PC you can use the IBM graphic character set to draw nicer looking ; arrows. On a color PC you can use different colors for arrows, for function, ; names, for argument values, and so on. (defconstant normal-att #x07) (defconstant arrow-att (if sys::*monitor-is-color* #x0e normal-att)) (defconstant enter-att (if sys::*monitor-is-color* #x0f normal-att)) (defconstant args-att (if sys::*monitor-is-color* #x0e normal-att)) (defconstant fname-att (if sys::*monitor-is-color* #x0b normal-att)) (defmacro with-attribute ((att &optional (stream '*terminal-io*)) &body body) `(let ((oldatt (send ,stream :attribute))) (unwind-protect (progn (send ,stream :set-attribute ,att) . ,body) (send ,stream :set-attribute oldatt)))) (defmacro with-dtrace-printer-settings (&body body) `(let ((*print-length* *dtrace-print-length*) (*print-level* *dtrace-print-level*) (*print-circle* *dtrace-print-circle*) (*print-array* *dtrace-print-array*) (*print-pretty* *dtrace-print-pretty*)) ,@body)) (defparameter *entry-arrow-string* '(201. 205. 205. 175.)) (defparameter *vertical-arrow* '(186.)) (defparameter *exit-arrow-string* '(200. 205. 205. 175.)) (defun send-char (stream x) (dolist (i x) (with-attribute (arrow-att *trace-output*) (send stream :write-char i)))) (defparameter *trace-wraparound* 15) (defun display-function-entry (name &optional ftype) (space-over) (draw-entry-arrow) (with-attribute (enter-att *trace-output*) (format *trace-output* "Enter ")) (with-attribute (fname-att *trace-output*) (format *trace-output* "~S" name)) (if (eq ftype 'macro) (format *trace-output* " macro"))) (defun display-one-arg (val name) (space-over) (with-attribute (args-att *trace-output*) (if (and (listp (type-of name)) (equal (butlast (type-of name)) '(vector string-char))) (format *trace-output* " ~A ~S" name val) (format *trace-output* (case (type-of name) (keyword " ~S ~S") (string " ~A ~S") (t " ~S = ~S")) name val)))) (defun display-arg-numeric (val num) (space-over) (with-attribute (args-att *trace-output*) (format *trace-output* " Arg-~D = ~S" num val))) (defun display-function-return (name results &optional ftype) (with-dtrace-printer-settings (space-over) (draw-exit-arrow) (with-attribute (fname-att *trace-output*) (format *trace-output* "~S " name)) (with-attribute (enter-att *trace-output*) (format *trace-output* "~A" (if (eq ftype 'macro) "expanded to" "returned"))) (with-attribute (args-att *trace-output*) (cond ((null results)) ((null (rest results)) (format *trace-output* " ~S" (first results))) (t (format *trace-output* " values ~{~S, ~}~s" (butlast results) (car (last results)))))))) (defun space-over () (format *trace-output* "~&") (dotimes (i (mod *trace-level* *trace-wraparound*)) (send-char *trace-output* *vertical-arrow*) (format *trace-output* " "))) (defun draw-entry-arrow () (send-char *trace-output* *entry-arrow-string*)) (defun draw-exit-arrow () (send-char *trace-output* *exit-arrow-string*)) ;;; Other implementation-specific routines go here. ; The function FETCH-ARGLIST is implementation-specific. It returns the ; formal argument list of a function, exactly as the list would appear in ; a DEFUN or lambda expression. ; ; The version below is for Golden Common Lisp. (defun fetch-arglist (x) (if (macro-function x) ;; then x is defmacro '(&rest "Form =") ;; else it is normal (read-from-string (sys:lambda-list x))))