;;;-*-Mode: LISP; Package: CCL -*- ;;; ;;; Copyright (C) 2009 Clozure Associates ;;; Copyright (C) 1994-2001 Digitool, Inc ;;; This file is part of Clozure CL. ;;; ;;; Clozure CL is licensed under the terms of the Lisp Lesser GNU Public ;;; License , known as the LLGPL and distributed with Clozure CL as the ;;; file "LICENSE". The LLGPL consists of a preamble and the LGPL, ;;; which is distributed with Clozure CL as the file "LGPL". Where these ;;; conflict, the preamble takes precedence. ;;; ;;; Clozure CL is referenced in the preamble as the "LIBRARY." ;;; ;;; The LLGPL is also available online at ;;; http://opensource.franz.com/preamble.html ;;; backtrace.lisp ;;; low-level support for stack-backtrace printing (in-package "CCL") #+ppc-target (require "PPC-BACKTRACE") #+x86-target (require "X86-BACKTRACE") #+arm-target (require "ARM-BACKTRACE") (defparameter *backtrace-show-internal-frames* nil) (defparameter *backtrace-print-level* 2) (defparameter *backtrace-print-length* 5) (defparameter *backtrace-format* :traditional "If :TRADITIONAL, shows calls to non-toplevel functions using FUNCALL, and shows frame address values. If :DIRECT, uses a more streamlined format.") (defun context-for-suspended-tcr (tcr) (let ((frame-ptr (%tcr-frame-ptr tcr))) (new-backtrace-info nil frame-ptr ;; youngest - not used frame-ptr ;; oldest - not used tcr nil ;; condition - not used frame-ptr ;; current #+ppc-target *fake-stack-frames* #+(or x86-target arm-target) frame-ptr (%fixnum-ref tcr (- target::tcr.db-link target::tcr-bias)) 0 ;; break level - not used ))) (defun backtrace-as-list (&key context process origin (count target::target-most-positive-fixnum) (start-frame-number 0) (print-level *backtrace-print-level*) (print-length *backtrace-print-length*) (show-internal-frames *backtrace-show-internal-frames*)) "Returns a list representing the backtrace. Each element in the list is a list that describes the call in one stack frame: (function arg1 arg2 ...) The arguments are represented by strings, the function is a symbol or a function object." (let* ((*backtrace-print-level* print-level) (*backtrace-print-length* print-length) (*backtrace-format* :list) (result nil)) (map-call-frames (lambda (p context) (multiple-value-bind (lfun pc) (cfp-lfun p) (push (if lfun (backtrace-call-arguments context p lfun pc) "?????") result))) :context context :process process :origin origin :count count :start-frame-number start-frame-number :test (and (not show-internal-frames) 'function-frame-p)) (nreverse result))) (defun print-call-history (&key context process origin (detailed-p t) (count target::target-most-positive-fixnum) (start-frame-number 0) (stream *debug-io*) (print-level *backtrace-print-level*) (print-length *backtrace-print-length*) (show-internal-frames *backtrace-show-internal-frames*) (format *backtrace-format*)) (let ((*backtrace-print-level* print-level) (*backtrace-print-length* print-length) (*backtrace-format* format) (*standard-output* stream) (*print-circle* nil) (frame-number (or start-frame-number 0))) (map-call-frames (lambda (p context) (multiple-value-bind (lfun pc) (cfp-lfun p) (unless (and (typep detailed-p 'fixnum) (not (= (the fixnum detailed-p) frame-number))) (%show-stack-frame-label frame-number p context lfun pc detailed-p) (when detailed-p (if (or (eq detailed-p :raw) (null lfun)) (%show-stack-frame p context lfun pc) (%show-args-and-locals p context lfun pc))) (incf frame-number)))) :context context :process process :origin origin :count count :start-frame-number start-frame-number :test (and (not show-internal-frames) 'function-frame-p)) (values))) (defun function-frame-p (p context) (and (not (catch-csp-p p context)) (cfp-lfun p))) (defun map-call-frames (fn &key context process origin (count target::target-most-positive-fixnum) (start-frame-number 0) test) (when (and context process (neq (bt.tcr context) (process-tcr process))) (error "Context ~s doesn't correspond to the process ~s" context process)) (let ((tcr (cond (context (bt.tcr context)) (process (process-tcr process)) (t (%current-tcr)))) (*print-catch-errors* t) (*signal-printing-errors* nil)) (if (eq tcr (%current-tcr)) (%map-call-frames-internal fn context (or origin (%get-frame-ptr)) count start-frame-number test) (unwind-protect (progn (%suspend-tcr tcr) (when (null context) (setq context (context-for-suspended-tcr tcr))) (%map-call-frames-internal fn context (or origin (bt.current context)) count start-frame-number test)) (%resume-tcr tcr)))) nil) ; RAW case (defun %show-stack-frame (p context lfun pc) (handler-case (multiple-value-bind (count vsp parent-vsp) (count-values-in-frame p context) (declare (fixnum count)) (dotimes (i count) (multiple-value-bind (var type name) (nth-value-in-frame p i context lfun pc vsp parent-vsp) (format t "~& ~D " i) (when name (format t "~s" name)) (let* ((*print-length* *backtrace-print-length*) (*print-level* *backtrace-print-level*)) (format t ": ~s" var)) (when type (format t " (~S)" type))))) (error () (format t "#"))) (terpri) (terpri)) (defun %show-args-and-locals (p context lfun pc) (handler-case (let* ((unavailable (cons nil nil))) (multiple-value-bind (args locals) (arguments-and-locals context p lfun pc unavailable) (case *backtrace-format* (:direct (format t "~& Arguments: ~:s" (arglist-from-map lfun))) (t (format t "~& ~s" (arglist-from-map lfun)))) (let* ((*print-length* *backtrace-print-length*) (*print-level* *backtrace-print-level*)) (flet ((show-pair (pair prefix) (destructuring-bind (name . val) pair (format t "~&~a~s: " prefix name) (if (eq val unavailable) (format t "#") (format t "~s" val))))) (case *backtrace-format* (:direct (when args (dolist (arg args) (show-pair arg " "))) (when locals ;; This shows all bindings (including specials), but help on debugger ;; commands refers to "locals", so say both words... (format t "~& Local bindings:") (dolist (loc locals) (show-pair loc " ")))) (t (dolist (arg args) (show-pair arg " ")) (terpri) (terpri) (dolist (loc locals) (show-pair loc " ")))))))) (error () (format t "#"))) (terpri) (terpri)) (defun backtrace-call-arguments (context cfp lfun pc) (nconc (let* ((name (function-name lfun))) (if (function-is-current-definition? lfun) (list name) (case *backtrace-format* (:direct (list (format nil "~s" (or name lfun)))) (:list (list 'funcall (format nil "~s" (or name lfun)))) (t (list 'funcall `(function ,(concatenate 'string "#<" (%lfun-name-string lfun) ">"))))))) (backtrace-supplied-args context cfp lfun pc))) (defun backtrace-supplied-args (context frame lfun pc) (multiple-value-bind (args valid) (supplied-argument-list context frame lfun pc) (if (not valid) '("???") (loop for arg in args collect (if (eq arg (%unbound-marker)) "?" (let* ((*print-length* *backtrace-print-length*) (*print-level* *backtrace-print-level*)) (format nil "~s" arg))))))) ;;; Return a list of "interesting" frame addresses in context, most ;;; recent first. (defun %stack-frames-in-context (context &optional (include-internal *backtrace-show-internal-frames*)) (collect ((frames)) (do* ((p (bt.youngest context) (parent-frame p context)) (q (bt.oldest context))) ((eql p q) (frames)) (when (or (not (catch-csp-p p context)) include-internal) (when (or (cfp-lfun p) include-internal) (frames p)))))) (defun %map-call-frames-internal (fn context origin count skip-initial test) (when (null skip-initial) (setq skip-initial 0)) (when (null count) (setq count target::target-most-positive-fixnum)) (unless (eq (last-frame-ptr context origin) (last-frame-ptr context)) (error "Origin ~s is not in the stack of ~s" origin context)) (let ((q (last-frame-ptr context)) (frame-number 0)) (do ((p origin (parent-frame p context))) ((or (null p) (eq p q) (%stack< q p context) (<= count 0)) nil) (when (or (null test) (funcall test p context)) (when (<= skip-initial frame-number) (funcall fn p context) (decf count)) (incf frame-number))))) (defun %show-stack-frame-label (frame-number p context lfun pc detailed-p) (case *backtrace-format* (:direct (let ((call (backtrace-call-arguments context p lfun pc))) (format t "~&~3D: ~a ~a~@d~:[~; [Exception]~]" frame-number (if lfun (if detailed-p (car call) call) "") "at pc " pc (exception-frame-p p)))) (t (format t "~&~c(~x) : ~D ~a ~d" (if (exception-frame-p p) #\* #\space) (index->address p) frame-number (if lfun (backtrace-call-arguments context p lfun pc)) pc)))) (defun %access-lisp-data (vstack-index) (%fixnum-ref vstack-index)) (defun %store-lisp-data (vstack-index value) (setf (%fixnum-ref vstack-index) value)) (defun closed-over-value (data) (if (closed-over-value-p data) (uvref data 0) data)) (defun set-closed-over-value (value-cell value) (setf (uvref value-cell 0) value)) ;;; Act as if VSTACK-INDEX points at some lisp data & return that data. (defun access-lisp-data (vstack-index) (closed-over-value (%access-lisp-data vstack-index))) (defun find-local-name (cellno lfun pc) (let* ((n cellno)) (when lfun (multiple-value-bind (mask where) (registers-used-by lfun pc) (if (and where (< (1- where) n (+ where (logcount mask)))) (let ((j *saved-register-count*)) (decf n where) (loop (loop (if (logbitp (decf j) mask) (return))) (if (< (decf n) 0) (return))) (values (format nil "saved ~a" (aref *saved-register-names* j)) nil)) (multiple-value-bind (nreq nopt restp nkeys junk optinitp junk ncells nclosed) (if lfun (function-args lfun)) (declare (ignore junk optinitp)) (if nkeys (setq nkeys (+ nkeys nkeys))) (values (if (and ncells (< n ncells)) (if (< n nclosed) :inherited (if (< (setq n (- n nclosed)) nreq) "required" (if (< (setq n (- n nreq)) nopt) "optional" (progn (setq n (- n nopt)) (progn (if (and nkeys (< n nkeys)) (if (not (logbitp 0 n)) ; a keyword "keyword" "key-supplied-p") (progn (if nkeys (setq n (- n nkeys))) (if (and restp (zerop n)) "rest" "opt-supplied-p"))))))))) (match-local-name cellno (function-symbol-map lfun) pc)))))))) (defun map-entry-value (context cfp lfun pc idx unavailable) (declare (fixnum pc idx)) (let* ((info (function-symbol-map lfun))) (if (null info) unavailable (let* ((addrs (cdr info)) (i (* 3 idx)) (addr (uvref addrs i)) (startpc (uvref addrs (the fixnum (+ i 1)))) (endpc (uvref addrs (the fixnum (+ i 2))))) (declare (fixnum i addr startpc endpc)) (if (or (< pc startpc) (>= pc endpc)) unavailable (let* ((value (if (= #o77 (ldb (byte 6 0) addr)) (raw-frame-ref cfp context (ash addr (- (+ target::word-shift 6))) unavailable) (find-register-argument-value context cfp addr unavailable)))) (if (typep value 'value-cell) (uvref value 0) value))))))) ;;; Returns non-nil on success (not newval) (defun set-map-entry-value (context cfp lfun pc idx newval) (declare (fixnum pc idx)) (let* ((unavailable (cons nil nil)) (value (map-entry-value context cfp lfun pc idx unavailable))) (if (eq value unavailable) nil (if (typep value 'value-cell) (progn (setf (uvref value 0) newval) t) (let* ((addrs (cdr (function-symbol-map lfun))) (addr (uvref addrs (the fixnum (* 3 idx))))) (declare (fixnum addr)) (if (= #o77 (ldb (byte 6 0) addr)) (raw-frame-set cfp context (ash addr (- (+ target::word-shift 6))) newval) (set-register-argument-value context cfp addr newval)) t))))) (defun argument-value (context cfp lfun pc name &optional (quote t)) (declare (fixnum pc)) (let* ((info (function-symbol-map lfun)) (unavailable (%unbound-marker))) (if (null info) unavailable (let* ((names (car info)) (addrs (cdr info))) (do* ((nname (1- (length names)) (1- nname)) (naddr (- (length addrs) 3) (- naddr 3))) ((or (< nname 0) (< naddr 0)) unavailable) (declare (fixnum nname naddr)) (when (eq (svref names nname) name) (let* ((value (let* ((addr (uvref addrs naddr)) (startpc (uvref addrs (the fixnum (1+ naddr)))) (endpc (uvref addrs (the fixnum (+ naddr 2))))) (declare (fixnum addr startpc endpc)) (if (or (< pc startpc) (>= pc endpc)) unavailable (if (= #o77 (ldb (byte 6 0) addr)) (raw-frame-ref cfp context (ash addr (- (+ target::word-shift 6))) unavailable) (find-register-argument-value context cfp addr unavailable)))))) (if (typep value 'value-cell) (setq value (uvref value 0))) (if (or (not quote) (self-evaluating-p value)) (return value) (return (list 'quote value)))))))))) (defun raw-frame-ref (cfp context index bad) (%raw-frame-ref cfp context index bad)) (defun raw-frame-set (cfp context index new) (%raw-frame-set cfp context index new)) (defun find-register-argument-value (context cfp regval bad) (%find-register-argument-value context cfp regval bad)) (defun set-register-argument-value (context cfp regval newval) (%set-register-argument-value context cfp regval newval)) (defun dbg-form (frame-number) (when *break-frame* (let* ((cfp (nth-raw-frame frame-number *break-frame* nil))) (if (and cfp (not (catch-csp-p cfp nil))) (multiple-value-bind (function pc) (cfp-lfun cfp) (if (and function (function-is-current-definition? function)) (block %cfp-form (collect ((form)) (multiple-value-bind (nreq nopt restp keys allow-other-keys optinit lexprp ncells nclosed) (function-args function) (declare (ignore ncells)) (unless (or lexprp restp (> 0 nclosed) (> 0 nopt) keys allow-other-keys optinit) (let* ((name (function-name function))) (multiple-value-bind (arglist win) (arglist-from-map function) (when (and win name (symbolp name)) (form name) (dotimes (i nreq) (let* ((val (argument-value nil cfp function pc (pop arglist)))) (if (closed-over-value-p val) (setq val (%svref val target::value-cell.value-cell))) (if (eq val (%unbound-marker)) (return-from %cfp-form nil)) (form val)))))))) (form))))))))) (defun function-args (lfun) "Returns 9 values, as follows: req = number of required arguments opt = number of optional arguments restp = t if rest arg keys = number of keyword arguments or NIL if &key not mentioned allow-other-keys = t if &allow-other-keys present optinit = t if any optional arg has non-nil default value or supplied-p variable lexprp = t if function is a lexpr, in which case all other values are undefined. ncells = number of stack frame cells used by all arguments. nclosed = number of inherited values (now counted distinctly from required) All numeric values (but ncells) are mod 64." (let* ((bits (lfun-bits lfun)) (req (ldb $lfbits-numreq bits)) (opt (ldb $lfbits-numopt bits)) (restp (logbitp $lfbits-rest-bit bits)) (keyvect (lfun-keyvect lfun)) (keys (and keyvect (length keyvect))) (allow-other-keys (logbitp $lfbits-aok-bit bits)) (optinit (logbitp $lfbits-optinit-bit bits)) (lexprp (logbitp $lfbits-restv-bit bits)) (nclosed (ldb $lfbits-numinh bits))) (values req opt restp keys allow-other-keys optinit lexprp (unless (or lexprp) (+ req opt (if restp 1 0) (if keys (+ keys keys) 0) (if optinit opt 0) nclosed)) nclosed))) ;;; If we can tell reliably, return the function's minimum number of ;;; non-inherited arguments, the maximum number of such arguments (or NIL), ;;; and the actual number of such arguments. We "can't tell" if either ;;; of the arguments to this function are null, and we can't tell reliably ;;; if any of the lfbits fields are full. (defun min-max-actual-args (fn nargs) (let* ((lfbits (if (and fn nargs) (lfun-bits fn) -1)) (raw-req (ldb $lfbits-numreq lfbits)) (raw-opt (ldb $lfbits-numopt lfbits)) (raw-inh (ldb $lfbits-numinh lfbits))) (declare (fixnum raw-req raw-opt raw-inh)) (if (or (eql raw-req (1- (ash 1 (byte-size $lfbits-numreq)))) (eql raw-opt (1- (ash 1 (byte-size $lfbits-numopt)))) (eql raw-inh (1- (ash 1 (byte-size $lfbits-numinh))))) (values nil nil nil) (values raw-req (unless (or (lfun-keyvect fn) (logbitp $lfbits-rest-bit lfbits) (logbitp $lfbits-restv-bit lfbits)) (+ raw-req raw-opt)) (- nargs raw-inh))))) (defun closed-over-value-p (value) (eql target::subtag-value-cell (typecode value))) (defun variables-in-scope (lfun pc) ;; Return a list of all symbol names "in scope" in the function lfun ;; at relative program counter PC, using the function's symbol map. ;; The list will be ordered so that least-recent bindings appear first. ;; Return a list of the matching symbol map entries as a second value (when pc (locally (declare (fixnum pc)) (let* ((map (function-symbol-map lfun)) (names (car map)) (info (cdr map))) (when map (let* ((vars ()) (indices ())) (dotimes (i (length names) (values vars indices)) (let* ((start-pc (aref info (1+ (* 3 i)))) (end-pc (aref info (+ 2 (* 3 i))))) (declare (fixnum start-pc end-pc)) (when (and (>= pc start-pc) (< pc end-pc)) (push i indices) (push (svref names i) vars)))))))))) (defun arg-value (context cfp lfun pc unavailable name) (multiple-value-bind (vars map-indices) (variables-in-scope lfun pc) (multiple-value-bind (valid req opt rest keys) (arg-names-from-map lfun pc) (if valid (let* ((nargs (+ (length req) (length opt) (if rest 1 0) (length keys))) (pos (position name vars))) (if (and pos (< pos nargs)) (map-entry-value context cfp lfun pc (nth pos map-indices) unavailable) unavailable)) unavailable)))) (defun local-value (context cfp lfun pc unavailable name) (multiple-value-bind (vars map-indices) (variables-in-scope lfun pc) (multiple-value-bind (valid req opt rest keys) (arg-names-from-map lfun pc) (if valid (let* ((nargs (+ (length req) (length opt) (if rest 1 0) (length keys))) (names (nthcdr nargs vars)) (indices (nthcdr nargs map-indices)) (pos (if (typep name 'unsigned-byte) name (position name names :from-end t)))) (if pos (map-entry-value context cfp lfun pc (nth pos indices) unavailable) unavailable)) unavailable)))) (defun set-arg-value (context cfp lfun pc name new) (multiple-value-bind (vars map-indices) (variables-in-scope lfun pc) (multiple-value-bind (valid req opt rest keys) (arg-names-from-map lfun pc) (if valid (let* ((nargs (+ (length req) (length opt) (if rest 1 0) (length keys))) (pos (position name vars))) (when (and pos (< pos nargs)) (set-map-entry-value context cfp lfun pc (nth pos map-indices) new))))))) (defun set-local-value (context cfp lfun pc name new) (multiple-value-bind (vars map-indices) (variables-in-scope lfun pc) (multiple-value-bind (valid req opt rest keys) (arg-names-from-map lfun pc) (if valid (let* ((nargs (+ (length req) (length opt) (if rest 1 0) (length keys))) (names (nthcdr nargs vars)) (indices (nthcdr nargs map-indices)) (pos (if (typep name 'unsigned-byte) name (position name names :from-end t)))) (if (and pos (< pos nargs)) (set-map-entry-value context cfp lfun pc (nth pos indices) new))))))) (defun arguments-and-locals (context cfp lfun pc &optional unavailable) (multiple-value-bind (vars map-indices) (variables-in-scope lfun pc) (collect ((args) (inherited-indices) (inherited-vars) (locals)) (multiple-value-bind (valid req opt rest keys) (arg-names-from-map lfun pc) (when valid (let* ((numinh (ldb $lfbits-numinh (lfun-bits lfun)))) (dotimes (i numinh) (inherited-indices (pop map-indices)) (inherited-vars (pop vars)))) (let* ((nargs (+ (length req) (length opt) (if rest 1 0) (length keys))) (nlocals (- (length vars) nargs)) (local-vars (append (nthcdr nargs vars) (inherited-vars))) (local-indices (append (nthcdr nargs map-indices) (inherited-indices))) (arg-vars (if (<= nlocals 0) vars (nbutlast vars nlocals))) (arg-indices (if (<= nlocals 0) map-indices (nbutlast map-indices nlocals)))) (flet ((get-arg-value (name) (let* ((pos (position name arg-vars :test #'eq))) (when pos (args (cons name (map-entry-value context cfp lfun pc (nth pos arg-indices) unavailable)))))) (get-local-value (name) (when name (locals (cons name (map-entry-value context cfp lfun pc (pop local-indices) unavailable)))))) (dolist (name req) (get-arg-value name)) (dolist (name opt) (get-arg-value name)) (when rest (get-arg-value rest)) (dolist (name keys) (get-arg-value name)) (dolist (name local-vars) (get-local-value name))))) (values (args) (locals)))))) ;; Return list of supplied arguments, as best we can reconstruct it. (defun supplied-argument-list (context frame lfun pc) (if (null pc) (values nil nil) (if (<= pc target::arg-check-trap-pc-limit) (values (arg-check-call-arguments frame lfun) t) (multiple-value-bind (params valid) (arglist-from-map lfun) (if (not valid) (values nil nil) (let* ((args (arguments-and-locals context frame lfun pc)) ;overkill, but will do. (state :required) (result ())) (dolist (param params) (if (or (member param lambda-list-keywords) (eq param '&lexpr)) (setq state param) (let* ((pair (pop args)) (value (cdr pair))) (case state (&lexpr (with-list-from-lexpr (rest value) (dolist (r rest) (push r result))) (return)) (&rest (dolist (r value) (push r result)) (return)) (&key (push param result))) (push value result)))) (values (nreverse result) t))))))) (defun safe-cell-value (val) val) (defun closure-closed-over-values (closure) (when (typep closure 'compiled-lexical-closure) (let* ((inner (closure-function closure)) (nclosed (nth-value 8 (function-args inner))) (names (car (function-symbol-map inner)))) (when nclosed (collect ((cells)) (do* ((i (1- (length names)) (1- i)) (k 0 (1+ k)) (idx 2 (1+ idx))) ((= k nclosed) (reverse (cells))) (let* ((name (svref names i)) (imm (nth-immediate closure idx))) (cells (list name (if (closed-over-value-p imm) (closed-over-value imm) imm)))))))))) ;;; Find the oldest binding frame that binds the same symbol as ;;; FRAME in context. If found, return the saved value of that ;;; binding, else the value of the symbol in the context's thread. (defun oldest-binding-frame-value (context frame) (let* ((oldest nil) (binding-index (%fixnum-ref frame (ash 1 target::fixnum-shift)))) (do* ((db (db-link context) (%fixnum-ref db 0))) ((eq frame db) (if oldest (%fixnum-ref oldest (ash 2 target::fixnum-shift)) (let* ((symbol (binding-index-symbol binding-index))) (if context (symbol-value-in-tcr symbol (bt.tcr context)) (%sym-value symbol))))) (if (eq (%fixnum-ref db (ash 1 target::fixnum-shift)) binding-index) (setq oldest db))))) (defun (setf oldest-binding-frame-value) (new context frame) (let* ((oldest nil) (binding-index (%fixnum-ref frame (ash 1 target::fixnum-shift)))) (do* ((db (db-link context) (%fixnum-ref db 0))) ((eq frame db) (if oldest (setf (%fixnum-ref oldest (ash 2 target::fixnum-shift)) new) (let* ((symbol (binding-index-symbol binding-index))) (if context (setf (symbol-value-in-tcr symbol (bt.tcr context)) new) (%set-sym-value symbol new))))) (if (eq (%fixnum-ref db (ash 1 target::fixnum-shift)) binding-index) (setq oldest db))))) ;;; End of backtrace.lisp