866 lines
37 KiB
HTML
866 lines
37 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta name="generator" content=
|
||
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
||
<title>Debugging</title>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="A collection of examples of using Common Lisp">
|
||
<meta name="viewport" content=
|
||
"width=device-width, initial-scale=1">
|
||
<link rel="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<link rel="stylesheet" href=
|
||
"assets/style.css">
|
||
<script type="text/javascript" src=
|
||
"assets/highlight-lisp.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery-3.2.1.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery.toc/jquery.toc.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/toggle-toc.js">
|
||
</script>
|
||
|
||
<link rel="stylesheet" href=
|
||
"assets/github.css">
|
||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||
</head>
|
||
<body>
|
||
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – Debugging</h1>
|
||
<div id="logo-container">
|
||
<a href="index.html">
|
||
<img id="logo" src="assets/cl-logo-blue.png"/>
|
||
</a>
|
||
|
||
<div id="searchform-container">
|
||
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
||
<input id="searchField" type="text" value="" placeholder="Search...">
|
||
</form>
|
||
</div>
|
||
|
||
<div id="toc-container" class="toc-close">
|
||
<div id="toc-title">Table of Contents</div>
|
||
<ul id="toc" class="list-unstyled"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content-container">
|
||
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – Debugging</h1>
|
||
|
||
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
||
<!-- <p class="announce"> -->
|
||
<!-- 📢 🤶 ⭐ -->
|
||
<!-- <a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a> -->
|
||
<!-- <strong> -->
|
||
<!-- Recently added: 18 videos on MACROS. -->
|
||
<!-- </strong> -->
|
||
<!-- <a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>. -->
|
||
<!-- </p> -->
|
||
|
||
<p class="announce">
|
||
📢 New videos: <a href="https://www.youtube.com/watch?v=h_noB1sI_e8">web dev demo part 1</a>, <a href="https://www.youtube.com/watch?v=xnwc7irnc8k">dynamic page with HTMX</a>, <a href="https://www.youtube.com/watch?v=Zpn86AQRVN8">Weblocks demo</a>
|
||
</p>
|
||
|
||
<p class="announce-neutral">
|
||
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
||
</p>
|
||
|
||
|
||
<div id="content"
|
||
<p>You entered this new world of Lisp and now wonder: how can we debug
|
||
what’s going on? How is it more interactive than other platforms?
|
||
What does the interactive debugger bring, apart from stack traces?</p>
|
||
|
||
<h2 id="print-debugging">Print debugging</h2>
|
||
|
||
<p>Well of course we can use the famous technique of “print
|
||
debugging”. Let’s just recap a few print functions.</p>
|
||
|
||
<p><code>print</code> works, it prints a <code>read</code>able representation of its argument,
|
||
which means what is <code>print</code>ed can be <code>read</code> back in by the Lisp
|
||
reader. It accepts only one argument.</p>
|
||
|
||
<p><code>princ</code> focuses on an <em>aesthetic</em> representation.</p>
|
||
|
||
<p><code>(format t "~a" …)</code>, with the <em>aesthetic</em> directive, prints a string (in <code>t</code>, the standard output
|
||
stream) and returns nil, whereas <code>format nil …</code> doesn’t print anything
|
||
and returns a string. With many format controls we can print several
|
||
variables at once.</p>
|
||
|
||
<p><code>print</code> has this useful debugging feature that it prints <em>and</em> returns
|
||
the result form it was given as argument. You can intersperse <code>print</code>
|
||
statements in the middle of your algorithm, it won’t break it.</p>
|
||
|
||
<pre><code class="language-lisp">(+ 2 (print 40))
|
||
</code></pre>
|
||
|
||
<h2 id="logging">Logging</h2>
|
||
|
||
<p>Logging is already a good evolution from print debugging ;)</p>
|
||
|
||
<p><a href="https://github.com/sharplispers/log4cl/">log4cl</a> is the popular,
|
||
de-facto logging library although it isn’t the only one. Download it:</p>
|
||
|
||
<pre><code class="language-lisp">(ql:quickload "log4cl")
|
||
</code></pre>
|
||
|
||
<p>and let’s have a dummy variable:</p>
|
||
|
||
<pre><code class="language-lisp">(defvar *foo* '(:a :b :c))
|
||
</code></pre>
|
||
|
||
<p>We can use log4cl with its <code>log</code> nickname, then it is as simple to use as:</p>
|
||
|
||
<pre><code class="language-lisp">(log:info *foo*)
|
||
;; <INFO> [13:36:49] cl-user () - *FOO*: (:A :B :C)
|
||
</code></pre>
|
||
|
||
<p>We can interleave strings and expressions, with or without <code>format</code>
|
||
control strings:</p>
|
||
|
||
<pre><code class="language-lisp">(log:info "foo is " *foo*)
|
||
;; <INFO> [13:37:22] cl-user () - foo is *FOO*: (:A :B :C)
|
||
(log:info "foo is ~{~a~}" *foo*)
|
||
;; <INFO> [13:39:05] cl-user () - foo is ABC
|
||
</code></pre>
|
||
|
||
<p>With its companion library <code>log4slime</code>, we can interactively change
|
||
the log level:</p>
|
||
|
||
<ul>
|
||
<li>globally</li>
|
||
<li>per package</li>
|
||
<li>per function</li>
|
||
<li>and by CLOS methods and CLOS hierarchy (before and after methods)</li>
|
||
</ul>
|
||
|
||
<p>It is very handy, when we have a lot of output, to turn off the
|
||
logging of functions or packages we know to work, and thus narrowing
|
||
our search to the right area. We can even save this configuration and
|
||
re-use it in another image, be it on another machine.</p>
|
||
|
||
<p>We can do all this through commands, keyboard shortcuts and also through a
|
||
menu or mouse clicks.</p>
|
||
|
||
<p><img src="assets/log4cl.png" alt=""changing the log level with log4slime"" /></p>
|
||
|
||
<p>We invite you to read log4cl’s README.</p>
|
||
|
||
<h2 id="using-the-powerful-repl">Using the powerful REPL</h2>
|
||
|
||
<p>Part of the joy of Lisp is the excellent REPL. Its existence usually
|
||
delays the need to use other debugging tools, if it doesn’t annihilate
|
||
them for the usual routine.</p>
|
||
|
||
<p>As soon as we define a function, we can try it in the REPL. In Slime,
|
||
compile a function with <code>C-c C-c</code> (the whole buffer with <code>C-c C-k</code>),
|
||
switch to the REPL with <code>C-c C-z</code> and try it. Eventually enter the
|
||
package you are working on with <code>(in-package :your-package)</code>
|
||
or <code>C-c ~</code> (<code>slime-sync-package-and-default-directory</code>,
|
||
which will also change the default working directory to the package definition’s directory).</p>
|
||
|
||
<p>The feedback is immediate. There is no need to recompile everything,
|
||
nor to restart any process, nor to create a main function and define
|
||
command line arguments for use in the shell (which we can of course do later on
|
||
when needed).</p>
|
||
|
||
<p>We usually need to create some data to test our function(s). This is a
|
||
subsequent art of the REPL existence and it may be a new discipline
|
||
for newcomers. A trick is to write the test data alongside your
|
||
functions but below a <code>#+nil</code> feature test (or safer, <code>+(or)</code>: it is still possible that someone pushed <code>NIL</code> to the <code>*features*</code> list) so that only you can
|
||
manually compile them:</p>
|
||
|
||
<pre><code class="language-lisp">#+nil
|
||
(progn
|
||
(defvar *test-data* nil)
|
||
(setf *test-data* (make-instance 'foo …)))
|
||
</code></pre>
|
||
|
||
<p>When you load this file, <code>*test-data*</code> won’t exist, but you can
|
||
manually create it with <code>C-c C-c</code>.</p>
|
||
|
||
<p>We can define tests functions like this.</p>
|
||
|
||
<p>Some do similarly inside <code>#| … |#</code> comments.</p>
|
||
|
||
<p>All that being said, keep in mind to write unit tests when time comes ;)</p>
|
||
|
||
<h2 id="inspect-and-describe">Inspect and describe</h2>
|
||
|
||
<p>These two commands share the same goal, printing a description of an
|
||
object, <code>inspect</code> being the interactive one.</p>
|
||
|
||
<pre><code>(inspect *foo*)
|
||
|
||
The object is a proper list of length 3.
|
||
0. 0: :A
|
||
1. 1: :B
|
||
|
||
2. 2: :C
|
||
> q
|
||
</code></pre>
|
||
|
||
<p>We can also, in editors that support it, right-click on any object in
|
||
the REPL and <code>inspect</code> them (or <code>C-c I</code> on the object to inspect in Slime).
|
||
We are presented a screen where we can
|
||
dive deep inside the data structure and even change it.</p>
|
||
|
||
<p>Let’s have a quick look with a more interesting structure, an object:</p>
|
||
|
||
<pre><code class="language-lisp">(defclass foo ()
|
||
((a :accessor foo-a :initform '(:a :b :c))
|
||
(b :accessor foo-b :initform :b)))
|
||
;; #<STANDARD-CLASS FOO>
|
||
(make-instance 'foo)
|
||
;; #<FOO {100F2B6183}>
|
||
</code></pre>
|
||
|
||
<p>We right-click on the <code>#<FOO</code> object and choose “inspect”. We are
|
||
presented an interactive pane (in Slime):</p>
|
||
|
||
<p><img src="assets/slime-inspector.png" alt=""Slime's inspector, a textual window with buttons"" /></p>
|
||
|
||
<p>When we click or press enter on the line of slot A, we inspect it further:</p>
|
||
|
||
<pre><code>#<CONS {100F5E2A07}>
|
||
--------------------
|
||
A proper list:
|
||
0: :A
|
||
1: :B
|
||
2: :C
|
||
</code></pre>
|
||
|
||
<p>In LispWorks, we can use a graphical inspector:</p>
|
||
|
||
<p><img src="assets/lispworks-graphical-inspector.png" alt=""The LispWorks inspector window"" /></p>
|
||
|
||
<h2 id="trace">Trace</h2>
|
||
|
||
<p><a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_tracec.htm">trace</a> allows us to see when a
|
||
function was called, what arguments it received, and the value it
|
||
returned.</p>
|
||
|
||
<pre><code class="language-lisp">(defun factorial (n)
|
||
(if (plusp n)
|
||
(* n (factorial (1- n)))
|
||
1))
|
||
</code></pre>
|
||
|
||
<p>To start tracing a function, just call <code>trace</code> with the function name
|
||
(or several function names):</p>
|
||
|
||
<pre><code class="language-lisp">(trace factorial)
|
||
|
||
(factorial 2)
|
||
0: (FACTORIAL 3)
|
||
1: (FACTORIAL 2)
|
||
2: (FACTORIAL 1)
|
||
3: (FACTORIAL 0)
|
||
3: FACTORIAL returned 1
|
||
2: FACTORIAL returned 1
|
||
1: FACTORIAL returned 2
|
||
0: FACTORIAL returned 6
|
||
6
|
||
|
||
(untrace factorial)
|
||
</code></pre>
|
||
|
||
<p>To untrace all functions, just evaluate <code>(untrace)</code>.</p>
|
||
|
||
<p>To get a list of currently traced functions, evaluate <code>(trace)</code> with no arguments.</p>
|
||
|
||
<p>In Slime we have the shortcut <code>C-c M-t</code> to trace or untrace a
|
||
function.</p>
|
||
|
||
<p>If you don’t see recursive calls, that may be because of the
|
||
compiler’s optimizations. Try this before defining the function to be
|
||
traced:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (optimize (debug 3))) ;; or C-u C-c C-c to compile with maximal debug settings.
|
||
</code></pre>
|
||
|
||
<p>The output is printed to <code>*trace-output*</code> (see the CLHS).</p>
|
||
|
||
<h3 id="trace-options">Trace options</h3>
|
||
|
||
<p><code>trace</code> accepts options. For example, you can use <code>:break t</code> to invoke
|
||
the debugger at the start of the function, before it is called (more on break below):</p>
|
||
|
||
<pre><code class="language-lisp">(trace factorial :break t)
|
||
(factorial 2)
|
||
</code></pre>
|
||
|
||
<p>We can define many things in one call to <code>trace</code>. For instance,
|
||
options that appear before the first function name to trace are
|
||
<em>global</em>, they affect all traced functions that we add afterwards. Here,
|
||
<code>:break t</code> is set for every function that follows: <code>factorial</code>, <code>foo</code>
|
||
and <code>bar</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(trace :break t factorial foo bar)
|
||
</code></pre>
|
||
|
||
<p>On the contrary, if an option comes after a function name, it acts as
|
||
a <em>local</em> option, only for its <em>preceding</em> function. That’s how we first
|
||
did. Below <code>foo</code> and <code>bar</code> come after, they are not affected by <code>:break</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(trace factorial :break t foo bar)
|
||
</code></pre>
|
||
|
||
<p>But do you actually want to <code>break</code> <em>before</em> the function call or just
|
||
<em>after</em> it? With <code>:break</code> as with many options, you can choose. These
|
||
are the options for <code>:break</code>:</p>
|
||
|
||
<pre><code>:break form ;; before
|
||
:break-after form
|
||
:break-all form ;; before and after
|
||
</code></pre>
|
||
|
||
<p><code>form</code> can be any form that evaluates to true.</p>
|
||
|
||
<p>Note that we explained the trace function of SBCL. Other
|
||
implementations may have the same feature with another syntax and
|
||
other option names. For example, in LispWorks it is “:break-on-exit”
|
||
instead of “:break-after”, and we write <code>(trace (factorial :break t))</code>.</p>
|
||
|
||
<p>Below are some other options but first, a trick with <code>:break</code>.</p>
|
||
|
||
<h3 id="trace-options-break">Trace options: break</h3>
|
||
|
||
<p>The argument to an option can be any form. Here’s a trick, on SBCL, to
|
||
get the break window when we are about to call <code>factorial</code>
|
||
with 0. <code>(sb-debug:arg 0)</code> refers to <code>n</code>, the first argument.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (trace factorial :break (equal 0 (sb-debug:arg 0)))
|
||
;; WARNING: FACTORIAL is already TRACE'd, untracing it first.
|
||
;; (FACTORIAL)
|
||
</code></pre>
|
||
|
||
<p>Running it again:</p>
|
||
|
||
<pre><code>CL-USER> (factorial 3)
|
||
0: (FACTORIAL 3)
|
||
1: (FACTORIAL 2)
|
||
2: (FACTORIAL 1)
|
||
3: (FACTORIAL 0)
|
||
|
||
breaking before traced call to FACTORIAL:
|
||
[Condition of type SIMPLE-CONDITION]
|
||
|
||
Restarts:
|
||
0: [CONTINUE] Return from BREAK.
|
||
1: [RETRY] Retry SLIME REPL evaluation request.
|
||
2: [*ABORT] Return to SLIME's top level.
|
||
3: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1003551BC3}>)
|
||
|
||
Backtrace:
|
||
0: (FACTORIAL 1)
|
||
Locals:
|
||
N = 1 <---------- before calling (factorial 0), n equals 1.
|
||
</code></pre>
|
||
|
||
<h3 id="trace-options-trace-on-conditions-trace-if-called-from-another-function">Trace options: trace on conditions, trace if called from another function</h3>
|
||
|
||
<p><code>:condition</code> enables tracing only if the condition in <code>form</code> evaluates to true.</p>
|
||
|
||
<pre><code>:condition form
|
||
:condition-after form
|
||
:condition-all form
|
||
</code></pre>
|
||
|
||
<blockquote>
|
||
<p>If :condition is specified, then trace does nothing unless Form
|
||
evaluates to true at the time of the call. :condition-after is
|
||
similar, but suppresses the initial printout, and is tested when the
|
||
function returns. :condition-all tries both before and after.</p>
|
||
</blockquote>
|
||
|
||
<p><code>:wherein</code> can be super useful:</p>
|
||
|
||
<pre><code>:wherein Names
|
||
</code></pre>
|
||
|
||
<blockquote>
|
||
<p>If specified, Names is a function name or list of names. trace does nothing unless a call to one of those functions encloses the call to this function (i.e. it would appear in a backtrace.) Anonymous functions have string names like “DEFUN FOO”.</p>
|
||
</blockquote>
|
||
|
||
<pre><code>:report Report-Type
|
||
</code></pre>
|
||
|
||
<blockquote>
|
||
<p>If Report-Type is trace (the default) then information is reported
|
||
by printing immediately. If Report-Type is nil, then the only effect
|
||
of the trace is to execute other options (e.g. print or
|
||
break). Otherwise, Report-Type is treated as a function designator
|
||
and, for each trace event, funcalled with 5 arguments: trace depth
|
||
(a non-negative integer), a function name or a function object, a
|
||
keyword (:enter, :exit or :non-local-exit), a stack frame, and a
|
||
list of values (arguments or return values).</p>
|
||
</blockquote>
|
||
|
||
<p>See also <code>:print</code> to enrich the trace output.</p>
|
||
|
||
<p>It is expected that implementations extend <code>trace</code> with non-standard
|
||
options. And we didn’t list all available options, so please refer to
|
||
your implementation’s documentation:</p>
|
||
|
||
<ul>
|
||
<li><a href="http://www.sbcl.org/manual/index.html#Function-Tracing">SBCL trace</a></li>
|
||
<li><a href="https://ccl.clozure.com/manual/chapter4.2.html">CCL trace</a></li>
|
||
<li><a href="http://www.lispworks.com/documentation/lw80/lw/lw-tracer-ug-2.htm">LispWorks trace</a></li>
|
||
<li><a href="https://franz.com/support/documentation/current/doc/debugging.htm#tracer-1">Allegro trace</a></li>
|
||
</ul>
|
||
|
||
<h3 id="tracing-method-invocation">Tracing method invocation</h3>
|
||
|
||
<p>In SBCL, we can use <code>(trace foo :methods t)</code> to trace the execution order of method combination (before, after, around methods). For example:</p>
|
||
|
||
<pre><code class="language-lisp">(trace foo :methods t)
|
||
|
||
(foo 2.0d0)
|
||
0: (FOO 2.0d0)
|
||
1: ((SB-PCL::COMBINED-METHOD FOO) 2.0d0)
|
||
2: ((METHOD FOO (FLOAT)) 2.0d0)
|
||
3: ((METHOD FOO (T)) 2.0d0)
|
||
3: (METHOD FOO (T)) returned 3
|
||
2: (METHOD FOO (FLOAT)) returned 9
|
||
2: ((METHOD FOO :AFTER (DOUBLE-FLOAT)) 2.0d0)
|
||
2: (METHOD FOO :AFTER (DOUBLE-FLOAT)) returned DOUBLE
|
||
1: (SB-PCL::COMBINED-METHOD FOO) returned 9
|
||
0: FOO returned 9
|
||
9
|
||
</code></pre>
|
||
|
||
<p>It is also possible in CCL.</p>
|
||
|
||
<p>See the <a href="clos.html">CLOS</a> section for a tad more information.</p>
|
||
|
||
<h3 id="interactive-trace-dialog">Interactive Trace Dialog</h3>
|
||
|
||
<p>Both SLIME and SLY provide an <a href="https://slime.common-lisp.dev/doc/html/SLIME-Trace-Dialog.html#SLIME-Trace-Dialog">interactive view for traces</a> that features better visualization of traces, and also access to the arguments and return values in their real form, via inspectors, not just the printed representation.</p>
|
||
|
||
<p><img src="trace-dialog.png" alt="trace-dialog" title="Trace dialog" /></p>
|
||
|
||
<p>How it works: (the following instructions are for SLIME)</p>
|
||
|
||
<ol>
|
||
<li>Select the functions to trace using <code>M-x slime-trace-dialog-toggle-trace</code> bound to <code>C-c M-t</code>.</li>
|
||
<li>Evaluate code that calls the traced functions.</li>
|
||
<li>Open the trace dialog tool via <code>M-x slime-trace-dialog</code> bound to <code>C-c T</code>.</li>
|
||
<li>The list of traced functions appear under <code>Traced specs</code>.
|
||
Traces are fetched in batches. So use the the <code>[refresh]</code> button to update status information about tracing (number of available traces that can be fetched).</li>
|
||
<li>Then use either the <code>[fetch next batch]</code> or <code>[fetch all]</code> buttons to fetch the traces. Traces appear under <code>Traced specs</code> after that, and you can use the SLIME inspector to visualize their data (arguments and return values).</li>
|
||
<li>After more code that calls the traced functions is evaluated, repeat the process (go to step 4).</li>
|
||
</ol>
|
||
|
||
<p>But, that flow can get a bit tedious, because of the separation between updating the status of the traces and fetching them. Sometimes it is better to just fetch the traces without updating the status first. We can do that invoking the command <code>M-x slime-trace-dialog-fetch-traces</code> bound to <code>G</code>. So, instead of steps 4 and 5, just press <code>G</code> to update the user interface.</p>
|
||
|
||
<p>These are some of the Emacs commands bound to useful keys:</p>
|
||
|
||
<p><code>g</code>
|
||
<code>M-x slime-trace-dialog-fetch-status</code></p>
|
||
|
||
<pre><code>Update information on the trace collection and traced specs.
|
||
</code></pre>
|
||
|
||
<p><code>G</code>
|
||
<code>M-x slime-trace-dialog-fetch-traces</code></p>
|
||
|
||
<pre><code>Fetch the next batch of outstanding (not fetched yet) traces. With a C-u prefix argument, repeat until no more outstanding traces.
|
||
</code></pre>
|
||
|
||
<p><code>C-k</code>
|
||
<code>M-x slime-trace-dialog-clear-fetched-traces</code></p>
|
||
|
||
<pre><code>Prompt for confirmation, then clear all traces, both fetched and outstanding.
|
||
</code></pre>
|
||
|
||
<p>Finally, the arguments and return values for each trace entry are interactive buttons. Clicking them opens the SLIME inspector on them. Invoking <code>M-RET</code> <code>M-x slime-trace-dialog-copy-down-to-repl</code> returns them to the REPL for manipulation . The number left of each entry indicates its absolute position in the calling order, which might differ from display order in case multiple threads call the same traced function.</p>
|
||
|
||
<p><code>M-x slime-trace-dialog-hide-details-mode</code> hides arguments and return values so you can concentrate on the calling logic. Additionally, <code>M-x slime-trace-dialog-autofollow-mode</code> will automatically display additional detail about an entry when the cursor moves over it.</p>
|
||
|
||
<h2 id="the-interactive-debugger">The interactive debugger</h2>
|
||
|
||
<p>Whenever an exceptional situation happens (see
|
||
<a href="error_handling.html">error handling</a>), or when you ask for it (using <code>step</code> or <code>break</code>),
|
||
the interactive debugger pops up.</p>
|
||
|
||
<p>It presents the error message, the available actions (<em>restarts</em>),
|
||
and the backtrace. A few remarks:</p>
|
||
|
||
<ul>
|
||
<li>the restarts are programmable, we can create our own.</li>
|
||
<li>in Slime, press <code>v</code> on a stack trace frame to view the corresponding
|
||
source file location.</li>
|
||
<li>hit Enter (or <code>t</code>) on a frame to toggle more details,</li>
|
||
<li>use <code>e</code> to evaluate some code from within that frame,</li>
|
||
<li>hit <code>r</code> to restart a given frame (see below).</li>
|
||
<li>we can explore the functionality with the menu that should appear
|
||
in our editor.</li>
|
||
</ul>
|
||
|
||
<h3 id="compile-with-maximum-debugging-information">Compile with maximum debugging information</h3>
|
||
|
||
<p>Usually your compiler will optimize things out and this will reduce
|
||
the amount of information available to the debugger. For example
|
||
sometimes we can’t see intermediate variables of computations. We can
|
||
change the optimization choices with:</p>
|
||
|
||
<pre><code class="language-lisp">(declaim (optimize (speed 0) (space 0) (debug 3)))
|
||
</code></pre>
|
||
|
||
<p>and recompile our code. You can achieve the same with a handy shortcut: <code>C-u C-c C-c</code>: the form is compiled with maximum debug settings. You can on the contrary use a negative prefix argument (<code>M--</code>) to compile for speed. And use a numeric argument to set the setting to it (you should read the docstring of <code>slime-compile-defun</code>).</p>
|
||
|
||
<h2 id="step">Step</h2>
|
||
|
||
<p><a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_step.htm">step</a> is an interactive command with similar scope than <code>trace</code>. This:</p>
|
||
|
||
<pre><code class="language-lisp">;; note: we copied factorial over to a file, to have more debug information.
|
||
(step (factorial 3))
|
||
</code></pre>
|
||
|
||
<p>gives an interactive pane with available actions (restarts) and the backtrace:</p>
|
||
|
||
<pre><code>Evaluating call:
|
||
(FACTORIAL 3)
|
||
With arguments:
|
||
3
|
||
[Condition of type SB-EXT:STEP-FORM-CONDITION]
|
||
|
||
Restarts:
|
||
0: [STEP-CONTINUE] Resume normal execution <-------------------- stepping actions
|
||
1: [STEP-OUT] Resume stepping after returning from this function
|
||
2: [STEP-NEXT] Step over call
|
||
3: [STEP-INTO] Step into call
|
||
4: [RETRY] Retry SLIME REPL evaluation request.
|
||
5: [*ABORT] Return to SLIME's top level.
|
||
--more--
|
||
|
||
Backtrace:
|
||
0: (FACTORIAL 3) <----------- press Enter to fold/unfold. Fix your code and press "r" to restart it.
|
||
Locals:
|
||
N = 3 <----------- want to check? Move the point here and
|
||
press "e" to evaluate code on that frame.
|
||
|
||
1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET ((SB-IMPL::*STEP-OUT* :MAYBE)) (UNWIND-PROTECT (SB-IMPL::WITH-STEPPING-ENABLED #))) #S(SB-KERNEL:LEXENV :FUNS NIL :VARS NIL :BLOCKS NIL :TAGS NIL :TYPE-RESTRICTIONS ..
|
||
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (STEP (FACTORIAL 3)) #<NULL-LEXENV>)
|
||
3: (EVAL (STEP (FACTORIAL 3)))
|
||
--more--
|
||
|
||
</code></pre>
|
||
|
||
<p><em>(again, be sure you compiled your function with maximum debug
|
||
settings (see above). Otherwise, your compiler might do optimizations
|
||
under the hood and you might not see useful information such as local
|
||
variables, or you might not be able to step at all.)</em></p>
|
||
|
||
<p>You have many options here. If you are using Emacs (or any other
|
||
editor actually), keep in mind that you have a “SLDB” menu that shows
|
||
you the available actions, in addition to the step window.</p>
|
||
|
||
<ul>
|
||
<li>follow the restarts to <strong>continue stepping</strong>: continue the
|
||
execution, step out of this function, step into the function call
|
||
the point is on, step over to the next function call, or abort
|
||
everything. The shortcuts are:
|
||
<ul>
|
||
<li><code>c</code>: continue</li>
|
||
<li><code>s</code>: step</li>
|
||
<li><code>x</code>: step next</li>
|
||
<li><code>o</code>: step out</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p><strong>inspect the backtrace</strong> and the source code. You can go to the
|
||
source file with <code>v</code>, on each stackframe (each line of the
|
||
backtrace). Press <code>Enter</code> or <code>t</code> (“toggle details”) on the
|
||
stackframe to see more information, such as the function parameters
|
||
for this call. Use <code>n</code> and <code>p</code> to navigate, use <code>M-n</code> and <code>M-p</code> to
|
||
navigate to the next or previous stackframe <em>and</em> to open the
|
||
corresponding source file at the same time. The point will be placed
|
||
on the function being called.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>evaluate code from within the context</strong> of that stackframe. In
|
||
Slime, use <code>e</code> (“eval in frame” and <code>d</code> to pretty-pint the result) and
|
||
type a Lisp form. It will be executed in the context of the
|
||
stackframe the point is on. Look, you can even inspect variables and
|
||
have Slime open another inspector window. If you are on the first
|
||
frame (<code>0:</code>), press <code>i</code>, then “n” to inspect the intermediate
|
||
variable.</p>
|
||
</li>
|
||
<li><strong>resume execution</strong> from where you want. Use <code>r</code> to restart the
|
||
frame the point is on. For example, go change the source code
|
||
(without quitting the interactive debugger), re-compile it, re-run
|
||
the frame to see if it works better. You didn’t restart all the
|
||
program execution, you just restarted your program from a precise
|
||
point. Use <code>R</code> to return from a stackframe, by giving its return
|
||
value.</li>
|
||
</ul>
|
||
|
||
<div class="info-box info" style="margin-bottom: 1em">
|
||
<!-- if inside a <p> then bootstrap adds 10px padding to the bottom -->
|
||
<strong>NB:</strong> let's think about it, <strong>this is awesome!</strong> We just restarted our program from any point in time. If we work with long-running computations, we don't need to restart it from the start. We can change, re-compile our erroneous code and resume execution from where it is needed to pass, no more.
|
||
</div>
|
||
|
||
<p>Stepping is precious. However, if you find yourself inspecting the
|
||
behaviour of a function a lot, it may be a sign that you need to
|
||
simplify it and divide it in smaller pieces.</p>
|
||
|
||
<p>And again, <strong>LispWorks</strong> has a <strong>graphical stepper</strong>.</p>
|
||
|
||
<div class="info" style="background-color: #e7f3fe; border-left: 6px solid #2196F3; padding: 17px; margin-bottom: 1em;">
|
||
<!-- if inside a <p> then bootstrap adds 10px padding to the bottom -->
|
||
<strong>TIP:</strong> the <a href="https://github.com/mmontone/slime-breakpoints">slime-breakpoints</a> package adds stepping and breaking buttons to Slime too.
|
||
</div>
|
||
|
||
<p><img src="https://raw.githubusercontent.com/mmontone/slime-star/master/screenshots/toolbars.png" alt="" /></p>
|
||
|
||
<h3 id="resume-a-program-execution-from-anywhere-in-the-stack-demo">Resume a program execution from anywhere in the stack (demo)</h3>
|
||
|
||
<p>In <a href="https://www.youtube.com/watch?v=jBBS4FeY7XM">this video</a> you will
|
||
find a demo that shows the process explained above: how to fix a buggy
|
||
function and how to <strong>resume the program execution</strong> from anywhere in the
|
||
stack, without running everything from zero again. The video shows it
|
||
with Emacs and Slime, the Lem editor, both with SBCL.</p>
|
||
|
||
<p>They key point is to use <code>r</code> (<code>sldb-restart-frame</code>) on a stack frame to restart it.</p>
|
||
|
||
<!-- epub-exclude-start -->
|
||
|
||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/jBBS4FeY7XM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
|
||
|
||
<!-- epub-exclude-end -->
|
||
|
||
<h2 id="break">Break</h2>
|
||
|
||
<p>A call to
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_break.htm">break</a>
|
||
makes the program enter the debugger, from which we can inspect the
|
||
call stack, and do everything described above in the stepper.</p>
|
||
|
||
<h3 id="breakpoints-in-slime">Breakpoints in Slime</h3>
|
||
|
||
<p>Look at the <code>SLDB</code> menu, it shows navigation keys and available
|
||
actions. Of which:</p>
|
||
|
||
<ul>
|
||
<li><code>e</code> (<em>sldb-eval-in-frame</em>) prompts for an expression and evaluates
|
||
it in the selected frame. This is how we can explore our
|
||
intermediate variables</li>
|
||
<li><code>d</code> is similar with the addition of pretty printing the result</li>
|
||
</ul>
|
||
|
||
<p>Once we are in a frame and detect a suspicious behavior, we can even
|
||
re-compile a function at runtime and resume the program execution from
|
||
where it stopped (using the “step-continue” restart
|
||
or using <code>r</code> (“restart frame”) on a given stackframe).</p>
|
||
|
||
<p>See also the <a href="https://github.com/mmontone/slime-star">Slime-star</a> Emacs extension mentioned above to set breakpoints without code annotations.</p>
|
||
|
||
<h2 id="advise-and-watch">Advise and watch</h2>
|
||
|
||
<p><em>advise</em> and <em>watch</em> are available in some
|
||
implementations, like CCL
|
||
(<a href="https://ccl.clozure.com/manual/chapter4.3.html#Advising">advise</a> and
|
||
<a href="https://ccl.clozure.com/manual/chapter4.12.html#watched-objects">watch</a>)
|
||
and LispWorks. They do exist in
|
||
SBCL but are not exported. <code>advise</code> allows to modify a function without changing its
|
||
source, or to do something before or after its execution, similar
|
||
to CLOS method combination (before, after, around methods).</p>
|
||
|
||
<p><code>watch</code> will signal a condition when a thread attempts to write to an
|
||
object being watched. It can be coupled with the display of the
|
||
watched objects in a GUI.
|
||
For a certain class of bugs (someone is changing this value, but I
|
||
don’t know who), this can be extremely helpful.</p>
|
||
|
||
<h2 id="cross-referencing">Cross-referencing</h2>
|
||
|
||
<p>Your Lisp can tell you all the places where a function is referenced
|
||
or called, where a global variable is set, where a macro is expanded,
|
||
and so on. For example, <code>slime-who-calls</code> (<code>C-c C-w C-c</code> or the Slime > Cross-Reference menu) will show you all the places where a function is called.</p>
|
||
|
||
<p>See our Emacs page for a complete list of commands.</p>
|
||
|
||
<h2 id="sly-stepper-and-sly-stickers">SLY stepper and SLY stickers</h2>
|
||
|
||
<p>SLY has an improved
|
||
<a href="https://github.com/joaotavora/sly-stepper">stepper</a> and a unique
|
||
feature, <a href="https://joaotavora.github.io/sly/#Stickers">stickers</a>. You
|
||
mark a piece of code, you run your code, SLY captures the results for
|
||
each sticker and lets you examine the program execution
|
||
interactively. It allows to see what sticker was captured, or
|
||
not, so we can see at a glance the code coverage of that function
|
||
call.</p>
|
||
|
||
<p>They are a non-intrusive alternative to <code>print</code> and <code>break</code>.</p>
|
||
|
||
<h2 id="unit-tests">Unit tests</h2>
|
||
|
||
<p>Last but not least, automatic testing of functions in isolation might
|
||
be what you’re looking for! See the <a href="testing.html">testing</a> section and a list of
|
||
<a href="https://github.com/CodyReichert/awesome-cl#unit-testing">test frameworks and libraries</a>.</p>
|
||
|
||
<h2 id="remote-debugging">Remote debugging</h2>
|
||
|
||
<p>You can have your software running on a machine over the network,
|
||
connect to it and debug it from home, from your development
|
||
environment.</p>
|
||
|
||
<p>The steps involved are to start a <strong>Swank server</strong> on the remote machine (Swank is the backend companion of Slime), create an
|
||
ssh tunnel and connect to the Swank server from our editor. Then we
|
||
can browse and evaluate code on the running instance transparently.</p>
|
||
|
||
<p>To test this, let’s define a function that prints forever.</p>
|
||
|
||
<p>If needed, import the dependencies first:</p>
|
||
|
||
<pre><code class="language-lisp">(ql:quickload '("swank" "bordeaux-threads"))
|
||
</code></pre>
|
||
|
||
<pre><code class="language-lisp">;; a little common lisp swank demo
|
||
;; while this program is running, you can connect to it from
|
||
;; another terminal or machine
|
||
;; and change the definition of doprint to print something else out!
|
||
|
||
(require :swank)
|
||
(require :bordeaux-threads)
|
||
|
||
(defparameter *counter* 0)
|
||
|
||
(defun dostuff ()
|
||
(format t "hello world ~a!~%" *counter*))
|
||
|
||
(defun runner ()
|
||
(swank:create-server :port 4006)
|
||
(format t "we are past go!~%")
|
||
(bt:make-thread (lambda ()
|
||
(loop repeat 5 do
|
||
(sleep 5)
|
||
(dostuff)
|
||
(incf *counter*)))
|
||
:name "do-stuff"))
|
||
|
||
(runner)
|
||
</code></pre>
|
||
|
||
<p>On the server, we can run this code with</p>
|
||
|
||
<pre><code>sbcl --load demo.lisp
|
||
</code></pre>
|
||
|
||
<p>If you check with <code>(bt:all-threads)</code>, you’ll see your Swank server running on port 4006, as well
|
||
as the other thread ready to do stuff:</p>
|
||
|
||
<pre><code>(#<SB-THREAD:THREAD "do-stuff" RUNNING {10027CEDC3}>
|
||
#<SB-THREAD:THREAD "Swank Sentinel" waiting on:
|
||
#<WAITQUEUE {10027D0003}>
|
||
{10027CE8B3}>
|
||
#<SB-THREAD:THREAD "Swank 4006" RUNNING {10027CEB63}>
|
||
#<SB-THREAD:THREAD "main thread" RUNNING {1007C40393}>)
|
||
</code></pre>
|
||
|
||
<p>We do port forwarding on our development machine:</p>
|
||
|
||
<pre><code>ssh -L4006:127.0.0.1:4006 username@example.com
|
||
</code></pre>
|
||
|
||
<p>this will securely forward port 4006 on the server at example.com to
|
||
our local computer’s port 4006 (Swank only accepts connections from
|
||
localhost).</p>
|
||
|
||
<p>We connect to the running Swank with <code>M-x slime-connect</code>, choosing localhost for the host
|
||
and port 4006.</p>
|
||
|
||
<p>We can write new code:</p>
|
||
|
||
<pre><code class="language-lisp">(defun dostuff ()
|
||
(format t "goodbye world ~a!~%" *counter*))
|
||
(setf *counter* 0)
|
||
</code></pre>
|
||
|
||
<p>and eval it as usual with <code>C-c C-c</code> or <code>M-x slime-eval-region</code> for instance. The output should change.</p>
|
||
|
||
<p>That’s how Ron Garret debugged the Deep Space 1 spacecraft from the earth
|
||
in 1999:</p>
|
||
|
||
<blockquote>
|
||
<p>We were able to debug and fix a race condition that had not shown up during ground testing. (Debugging a program running on a $100M piece of hardware that is 100 million miles away is an interesting experience. Having a read-eval-print loop running on the spacecraft proved invaluable in finding and fixing the problem.</p>
|
||
</blockquote>
|
||
|
||
<h2 id="references">References</h2>
|
||
|
||
<ul>
|
||
<li><a href="https://successful-lisp.blogspot.com/p/httpsdrive.html">“How to understand and use Common Lisp”</a>, chap. 30, David Lamkins (book download from author’s site)</li>
|
||
<li><a href="https://malisper.me/debugging-lisp-part-1-recompilation/">Malisper: debugging Lisp series</a></li>
|
||
<li><a href="https://two-wrongs.com/debugging-common-lisp-in-slime.html">Two Wrongs: debugging Common Lisp in Slime</a></li>
|
||
<li><a href="https://common-lisp.net/project/slime/doc/html/Connecting-to-a-remote-lisp.html#Connecting-to-a-remote-lisp">Slime documentation: connecting to a remote Lisp</a></li>
|
||
<li><a href="http://cvberry.com/tech_writings/howtos/remotely_modifying_a_running_program_using_swank.html">cvberrycom: remotely modifying a running Lisp program using Swank</a></li>
|
||
<li><a href="http://www.flownet.com/gat/jpl-lisp.html#1994-1999%20-%20Remote%20Agent">Ron Garret: Lisping at the JPL</a></li>
|
||
<li><a href="https://www.youtube.com/watch?v=_gZK0tW8EhQ&feature=youtu.be&t=4175">the Remote Agent experiment: debugging code from 60 million miles away (youtube)</a> (<a href="https://www.reddit.com/r/lisp/comments/a7156w/lisp_and_the_remote_agent/">“AMA” on reddit</a>)</li>
|
||
</ul>
|
||
|
||
|
||
<p class="page-source">
|
||
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/debugging.md">debugging.md</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
|
||
// Don't write the TOC on the index.
|
||
if (window.location.pathname != "/cl-cookbook/") {
|
||
$("#toc").toc({
|
||
content: "#content", // will ignore the first h1 with the site+page title.
|
||
headings: "h1,h2,h3,h4"});
|
||
}
|
||
|
||
$("#two-cols + ul").css({
|
||
"column-count": "2",
|
||
});
|
||
$("#contributors + ul").css({
|
||
"column-count": "4",
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
<div>
|
||
<footer class="footer">
|
||
<hr/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">our contributor's Common Lisp video course on Udemy</a>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div id="toc-btn">T<br>O<br>C</div>
|
||
</div>
|
||
|
||
<script text="javascript">
|
||
HighlightLisp.highlight_auto({className: null});
|
||
</script>
|
||
|
||
<script type="text/javascript">
|
||
function duckSearch() {
|
||
var searchField = document.getElementById("searchField");
|
||
if (searchField && searchField.value) {
|
||
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
||
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
||
// https://duckduckgo.com/params
|
||
// kj=b2: blue header in results page
|
||
// kf=-1: no favicons
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
||
|
||
</body>
|
||
</html>
|