Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Notes re. debugging Common Lisp with SBCL and Slime
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