Every now and then I see complaints about the stacktraces in SBCL. They contain too little info, or too much info, or are formatted the wrong way, etc. But the backtrace printing isn't really any dark magic, it's just basic Lisp code. If you don't like the default format, just write a new backtrace function that prints something prettier/less cluttered/more informative/etc.
For inspiration, below is one implementation, based on a really quick
hack I wrote in answer to a c.l.l post a few weeks ago. In addition to
cosmetic changes, it adds a a couple of extra features: printing
filenames and line numbers for the frames when possible, and printing
the values of local variables when possible. Just call
backtrace-with-extra-info
in any condition handler where you'd
normally call sb-debug:backtrace
, or call it from the debugger REPL
instead of using the backtrace
debugger command.
The code assumes that you've got Swank loaded. For best results, compile
your code with (debug 2)
or higher.
(defun backtrace-with-extra-info (&key (start 1) (end 20))
(swank-backend::call-with-debugging-environment
(lambda ()
(loop for i from start to (length (swank-backend::compute-backtrace
start end))
do (ignore-errors (print-frame i))))))
(defun print-frame (i)
(destructuring-bind (&key file position &allow-other-keys)
(apply #'append
(remove-if #'atom
(swank-backend:frame-source-location-for-emacs i)))
(let* ((frame (swank-backend::nth-frame i))
(line-number (find-line-position file position frame)))
(format t "~2@a: ~s~%~
~:[~*~;~:[~2:* At ~a (unknown line)~*~%~;~
~2:* At ~a:~a~%~]~]~
~:[~*~; Local variables:~%~{ ~a = ~s~%~}~]"
i
(sb-debug::frame-call (swank-backend::nth-frame i))
file line-number
(swank-backend::frame-locals i)
(mapcan (lambda (x)
;; Filter out local variables whose variables we
;; don't know
(unless (eql (getf x :value) :<not-available>)
(list (getf x :name) (getf x :value))))
(swank-backend::frame-locals i))))))
(defun find-line-position (file char-offset frame)
;; It would be nice if SBCL stored line number information in
;; addition to form path information by default Since it doesn't
;; we need to use Swank to map the source path to a character
;; offset, and then map the character offset to a line number
(ignore-errors
(let* ((location (sb-di::frame-code-location frame))
(debug-source (sb-di::code-location-debug-source location))
(line (with-open-file (stream file)
(1+ (loop repeat char-offset
count (eql (read-char stream) #\Newline))))))
(format nil "~:[~a (file modified)~;~a~]"
(= (file-write-date file)
(sb-di::debug-source-created debug-source))
line))))
For example on the following code:
(declaim (optimize debug))
(defun foo (x)
(let ((y (+ x 3)))
(backtrace)
(backtrace-with-extra-info)
(+ x y)))
(defmethod bar ((n fixnum) (y (eql 1)))
(foo (+ y n)))
The old backtrace would look like:
1: (FOO 4)
2: ((SB-PCL::FAST-METHOD BAR (FIXNUM (EQL 1)))
#<unused argument>
#<unused argument>
3
1)
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (BAR 3 1) #<NULL-LEXENV>)
And the new backtrace like:
1: FOO
At /tmp/testlisp:5
Local variables:
X = 4
Y = 7
2: (SB-PCL::FAST-METHOD BAR (FIXNUM (EQL 1)))
At /tmp/testlisp:8
Local variables:
N = 3
Y = 1
3: SB-INT:SIMPLE-EVAL-IN-LEXENV
At /scratch/src/sbcl/src/code/evallisp:93 (file modified)
Local variables:
ARG-0 = (BAR 3 1)
ARG-1 = #<NULL-LEXENV>
An improvement? That's probably in the eye of the beholder, and depends on the codebase and the use cases. For example I can imagine that for large functions showing the values of local variables in the trace would make it way too spammy. But that's besides the point: if the default stacktrace format is making debugging difficult for you, it's not hard to customize it.
Actually, it was the backtraces more than anything else that made me give up on Lisp a couple of years ago. I would get some code starting to run in SBCL, then hit a problem, and I just found the stack traces to be so impenetrable and hostile compared to other languages (Java, Python) that eventually I just gave up.
Even this trace seems to complex. To my mind, there needs to be some version that pretty much just says:
FOO line 42 BAR line 69
etc.
Just try to cut the rest away, if you can.