Skip to content

Instantly share code, notes, and snippets.

@nikodemus
Created November 2, 2010 11:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nikodemus/659495 to your computer and use it in GitHub Desktop.
Save nikodemus/659495 to your computer and use it in GitHub Desktop.
Notes re. debugging Common Lisp with SBCL and Slime
Context from IRC logs for future reference.
[12:36] <kushal> nikodemus, can you point me to some tutorial on debugging on sbcl ?
[12:37] * andreer (andreer@flode.pvv.ntnu.no) has joined #quicklisp
[12:38] <nikodemus> kushal: no really good ones actually exist at the moment. i'm planning on writing one before the year is out, but... in the meanwhile, i can give some pointers
[12:38] <nikodemus> a couple of questions: is this a general question, or do you have a specific issue you need to debug?
[12:39] <kushal> right now , general, managed to debug the specific issue somehow :)
[12:39] <nikodemus> ok. are you using slime?
[12:39] <kushal> nikodemus, yes and no both
[12:40] <nikodemus> good. :)
[12:40] <kushal> some cases I am running with dynamic-size
[12:40] <nikodemus> ah
[12:40] <nikodemus> let me write down a few notes. be back in 5
[12:40] <kushal> nikodemus, ok thanks :)
[12:47] * pavelludiq (~quassel@87.246.31.34) has joined #quicklisp
[13:12] * arbscht_ is now known as arbscht
[13:13] * cmpitg has quit (Quit: leaving)
[13:22] <nikodemus> kushal: http://gist.github.com/659495 # took a bit longer, and it's still just a quick braindump
[13:23] <kushal> nikodemus, ok checking
[13:25] <kushal> nikodemus, ok,
[13:26] <kushal> nikodemus, how to find the exact line where the error happens in code ? source command never helped that much
[13:27] <nikodemus> kushal: let me think. i do that by instinct, so i'll have to check :)
[13:29] <nikodemus> v in the debugger should take you to the right frame
[13:29] <nikodemus> sorry, "on the right frame" should take you to the function responsible
[13:30] <kushal> ok
[13:30] <nikodemus> eg. if you have (/ x 0) somewhere, the frame of interest is the one after INTEGER-/-INTEGER or similar
[13:30] <kushal> ok
[13:31] <kushal> I was getting (SB-IMPL::GETHASH3 "i686" NIL NIL)
[13:31] <nikodemus> if the debug policy for that function is 2 or more, you should get the exact location
[13:31] <kushal> source showed #<SB-DI::COMPILED-DEBUG-FUN (SB-C::TL-XEP SB-IMPL::GETHASH3)> has no debug-block information.
[13:32] <kushal> nikodemus, how to set debug policy in my code ?
[13:32] <nikodemus> so you're trying to use NIL where there should be a hash-table -- you want the previous frame
[13:32] <kushal> up ?
[13:32] <kushal> or v ?
[13:32] <nikodemus> (defun foo () (declare (optimize (debug 2)) ...) locally or (declaim (optimize (debug 2)) per file -- it's actually more complex than "per file", but that's an approximation you can live with
[13:33] <nikodemus> the one below the the GETHASH frame in the backtrace, it's caller
[13:33] <nikodemus> that (or earlier) is where the error is
[13:33] <kushal> nikodemus, ok
[13:34] <nikodemus> slime v should be made to fall back on other methods if there is not debug block information, like most internals don't
[13:34] <kushal> nikodemus, after going one frame down, source command ?
[13:34] <nikodemus> in the repl?
[13:34] <kushal> yes ?
[13:34] <nikodemus> is that your function that is responsible for that frame?
[13:35] <kushal> nikodemus, let me introduce the bug again and try live :)
[13:35] <nikodemus> i don't actually almost ever use the source command in the plain sbcl repl, but probably
[13:36] <nikodemus> when you land in the sbcl debugger in the plain repl, a good first thing to do is "ba 30" -- get the backtrace for a modest number of frames to give you an idea of what's going on
[13:36] * mathrick has quit (Excess Flood)
[13:37] <kushal> nikodemus, yes, that is actually helped me to fix the error in the first place
0. Test case reduction is an essential skill, no matter the language
or the environment. It is the art of reducing the code that provokes
the issue (wrong result, an error, whatever) down to manageable size
-- including the full call path involved, and environmental issues
like Slime vs no Slime, or current directory. The smaller the better
in general, but it is a balancing act: if you can identify the issue
using other methods in five minutes, it doesn't make sense to spend an
hour or two boiling down the test case. ...but when other methods are
not producing results, and time is dragging on then you should
consider this.
1. Defensive programming. Not just coding to protect against errors,
but coding so that your code is easy to debug. Avoid IGNORE-ERRORS and
generally swallowing errors silently. Sometimes it is the right thing
to do, but the more you do it, the harder *BREAK-ON-SIGNALS* becomes
to use when you need it. Avoid SAFETY 0 like the plague -- it can hide
a multitude of sins. Avoid DEBUG 0 -- it doesn't pay. Write
PRINT-OBJECT methods for your objects, give your objects names to use
when printing. Check that slots are bound before you use them in your
PRINT-OBJECT methods. NEVER use INTERRUPT-THREAD or WITH-TIMEOUT
unless you really know what you are doing and exactly why I'm telling
you not to use them.
2. Stop to think. Read the error messages, if any, carefully.
Sometimes they're no help at all, but sometimes there are nuggets of
information in them that a casual glance will miss.
3. Know your environment. (This is what the question was really about, I know...)
3.0. M-. is gold. Plain v on a backtrace frame in Slime may also take
you places if your code has a sufficiently high debug setting, but M-.
should work pretty much always.
3.1. The Slime Inspector is one of my primary debugging tools -- but
that is probably affected by the kind of code I work on, so it might
not be the same for everyone. Still, you should familiarize yourself
with it -- and use the fancy one. :)
3.2. While SBCL backtraces aren't at the moment the pretties ones in
the world, try to make sense out of them. Just do (error "foo") in the
REPL, and figure out what is going on. Experiment with both the plain
SBCL debugger and the fancy Slime Debugger before you need to use them
for real. They'll feel a lot less hostile that way. I'll write advice
on interpreting the backtraces at another time.
3.3. Learn about *BREAK-ON-SIGNALS* and TRACE. Also note the SBCL
extensions to TRACE.
3.4. The stepper isn't really a debugging tool, IMO -- it is a tool
for understanding control flow, which sometimes helps in debugging --
but if you compile your code with DEBUG 3, then (STEP (FOO)) can take
you to places.
3.5. Learn about M-RET (macroexpand) in Slime. Discover what happens
if you do (SETF *PRINT-GENSYM* NIL) first, and understand the
potential danger there -- but also the utility of being easily able to
copy-paste the expansion into your test case when you're trying to
reduce it. (Replacing expansions of macros in the COMMON-LISP package
is typically pointless, but replacing those from user packages can be
golden.)
3.6. If all else fails, do (sb-ext:restrict-compiler-policy 'safety 3)
and (sb-ext:restrict-compiler-policy 'debug 3) and recompile your
code. Debugging should be easier now. If the error goes away, either
(a) you had a type-error or similar in SAFETY 0 code that was breaking
stuff but is now swallowed by an IGNORE-ERRORS or a HANDLER-CASE or
(b) you may have found on SBCL bug: compiler policy should not
generally speaking change codes behaviour -- though there are some
ANSI mandated things for SAFETY 3, and high DEBUG can inhibit
tail-call optimizations which, as Schemers know, can matter.
3.7. DISASSEMBLE isn't normally a debugging tool, but sometimes it can
help too. Depends on what the issue is.
4. Extensions to printf() debugging. Sometimes this is just the
easiest thing. No shame in there.
4.1. Special variables are nice.
(LET ((*DEBUG* :MAGIC)) ...) and elsewhere
(WHEN (EQ :MAGIC *DEBUG*) (PRINT (LIST :FOO FOO)))
Because I'm lazy, I tend to use * as the magic variable, so I can also
trivially set it in the REPL. This allows you to get the debugging
output for stuff you are interested in only when certain conditions
are true. Or you can use it to tell which code path the call is coming
from, etc.
4.2. Don't be afraid to add (break "foo=~S" foo) and similar calls to
the code you're debugging.
4.3. SB-DEBUG:BACKTRACE can sometimes be of use.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment