emacs.d/clones/lisp/stevelosh.com/blog/2017/01/chip8-debugging-infrastructure/index.html

421 lines
36 KiB
HTML
Raw Normal View History

2022-10-07 15:47:14 +02:00
<!DOCTYPE html>
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='http://stevelosh.com/static/css/style.css' rel='stylesheet' type='text/css' /><link href='http://stevelosh.com/static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>CHIP-8 in Common Lisp: Debugging Infrastructure / Steve Losh</title></head><body><header><a id='logo' href='http://stevelosh.com/'>Steve Losh</a><nav><a href='http://stevelosh.com/blog/'>Blog</a> - <a href='http://stevelosh.com/projects/'>Projects</a> - <a href='http://stevelosh.com/photography/'>Photography</a> - <a href='http://stevelosh.com/links/'>Links</a> - <a href='http://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>CHIP-8 in Common Lisp: Debugging Infrastructure</a></h1><p class='date'>Posted on January 5th, 2017.</p><p>Our <a href="https://en.wikipedia.org/wiki/CHIP-8">CHIP-8</a> emulator in Common Lisp is coming along nicely. It can play
games, and in the last post we added a disassembler so we can dump the code of
ROMs.</p>
<p>In this post we'll add some low-level debugging infrastructure so we can set
breakpoints and step through code.</p>
<p>The full series of posts so far:</p>
<ol>
<li><a href="../../../2016/12/chip8-cpu/index.html">CHIP-8 in Common Lisp: The CPU</a></li>
<li><a href="../../../2016/12/chip8-graphics/index.html">CHIP-8 in Common Lisp: Graphics</a></li>
<li><a href="../../../2016/12/chip8-input/index.html">CHIP-8 in Common Lisp: Input</a></li>
<li><a href="../../../2016/12/chip8-sound/index.html">CHIP-8 in Common Lisp: Sound</a></li>
<li><a href="../chip8-disassembly/index.html">CHIP-8 in Common Lisp: Disassembly</a></li>
<li><a href="index.html">CHIP-8 in Common Lisp: Debugging Infrastructure</a></li>
<li><a href="../chip8-menus/index.html">CHIP-8 in Common Lisp: Menus</a></li>
</ol>
<p>The full emulator source is on <a href="https://bitbucket.org/sjl/cl-chip8">BitBucket</a> and <a href="https://github.com/sjl/cl-chip8">GitHub</a>.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-architecture">Architecture</a></li><li><a href="index.html#s2-the-debugger-data-structure">The Debugger Data Structure</a></li><li><a href="index.html#s3-pausing">Pausing</a></li><li><a href="index.html#s4-stepping">Stepping</a></li><li><a href="index.html#s5-printing">Printing</a></li><li><a href="index.html#s6-breakpoints">Breakpoints</a></li><li><a href="index.html#s7-result">Result</a></li><li><a href="index.html#s8-future">Future</a></li></ol>
<h2 id="s1-architecture"><a href="index.html#s1-architecture">Architecture</a></h2>
<p>The overall goal will be to keep the debugging infrastructure as separate from
the rest of the emulation code as possible. Unfortunately the nature of
debugging will require us to weave it into <em>some</em> of the normal emulator code,
but we'll try to keep the pollution to a minimum.</p>
<p>All information about the state of the debugger (e.g. breakpoints, pause status,
etc) will be stored in a separate debugger data structure. We'll define a small
API for interacting with this structure. The <code>chip</code> struct will have
a <code>debugger</code> slot and will use the debugger API to interact with it. Later the
graphical debugger UI will also use this API.</p>
<h2 id="s2-the-debugger-data-structure"><a href="index.html#s2-the-debugger-data-structure">The Debugger Data Structure</a></h2>
<p>We'll start by creating a <code>debugger</code> struct and a <code>with-debugger</code> macro for it
and will add fields to this struct as we build the debugging infrastructure:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> debugger
<span class="comment">; ...
</span> </span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">define-with-macro</span></i> debugger
<span class="comment">; ...
</span> </span>)</span></span></code></pre>
<p>We'll then add a <code>debugger</code> slot to our <code>chip</code> struct:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">debugger <span class="paren3">(<span class="code">make-debugger</span>)</span> <span class="keyword">:type</span> debugger <span class="keyword">:read-only</span> t</span>)</span></span>)</span></span></code></pre>
<p>It's read-only because we should never be swapping out a <code>chip</code>'s debugger as it
runs.</p>
<h2 id="s3-pausing"><a href="index.html#s3-pausing">Pausing</a></h2>
<p>The first thing we'll add is support for pausing and unpausing execution. We'll
add a <code>paused</code> slot to the debugger:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> debugger
<span class="paren2">(<span class="code">paused nil <span class="keyword">:type</span> boolean</span>)</span></span>)</span></span></code></pre>
<p>Then we'll make some simple API functions so the emulator won't have to directly
work with the debugger's slots:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-pause <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code">setf paused t</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-unpause <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code">setf paused nil</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-paused-p <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code">debugger-paused debugger</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-toggle-pause <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren3">(<span class="code">debugger-paused-p debugger</span>)</span>
<span class="paren3">(<span class="code">debugger-unpause debugger</span>)</span>
<span class="paren3">(<span class="code">debugger-pause debugger</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Notice how <code>debugger-toggle-pause</code> uses the lower-level API functions instead of
directly modifying the slot. This will become important shortly.</p>
<p>Now we need to start modifying the emulator itself to pause execution when the
debugger is paused. Unfortunately this is going to be pretty invasive, but
I don't think there's much of a way around that.</p>
<p>We'll change <code>emulate-cycle</code> to check if the debugger is paused before actually
running an instruction:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> emulate-cycle <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">debugger-paused-p debugger</span>)</span> <span class="comment">; NEW
</span> <span class="paren4">(<span class="code">sleep 10/1000</span>)</span> <span class="comment">; NEW
</span> <span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">instruction <span class="paren1">(<span class="code">cat-bytes
<span class="paren2">(<span class="code">aref memory program-counter</span>)</span>
<span class="paren2">(<span class="code">aref memory <span class="paren3">(<span class="code">1+ program-counter</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">zapf program-counter <span class="paren6">(<span class="code">chop 12 <span class="paren1">(<span class="code">+ % 2</span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">dispatch-instruction chip instruction</span>)</span></span>)</span></span>)</span>
nil</span>)</span></span>)</span></span></code></pre>
<p>The timer thread will need to skip the timer decrements whenever the debugger is
paused:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-timers <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">iterate
<span class="paren4">(<span class="code">while running</span>)</span>
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">not <span class="paren6">(<span class="code">debugger-paused-p debugger</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">decrement-timers chip</span>)</span></span>)</span>
<span class="paren4">(<span class="code">sleep 1/60</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Next we'll modify the sound thread to be silent when paused, otherwise pausing
during a buzz would result in perpetual buzzing:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-sound <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">portaudio:with-audio</span></i>
<span class="paren3">(<span class="code"><i><span class="symbol">portaudio:with-default-audio-stream</span></i>
<span class="paren4">(<span class="code">audio-stream 0 1
<span class="keyword">:sample-format</span> <span class="keyword">:float</span>
<span class="keyword">:sample-rate</span> +sample-rate+
<span class="keyword">:frames-per-buffer</span> +audio-buffer-size+</span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren5">(<span class="code">chip</span>)</span>
<span class="paren5">(<span class="code">iterate <span class="paren6">(<span class="code">with buffer = <span class="paren1">(<span class="code">make-audio-buffer</span>)</span></span>)</span>
<span class="paren6">(<span class="code">with angle = 0.0</span>)</span>
<span class="paren6">(<span class="code">with rate = <span class="paren1">(<span class="code">audio-rate 440</span>)</span></span>)</span>
<span class="paren6">(<span class="code">while running</span>)</span>
<span class="paren6">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren1">(<span class="code">and <span class="paren2">(<span class="code">plusp sound-timer</span>)</span>
<span class="paren2">(<span class="code">not <span class="paren3">(<span class="code">debugger-paused-p debugger</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren1">(<span class="code"><i><span class="symbol">progn</span></i>
<span class="paren2">(<span class="code">setf angle <span class="paren3">(<span class="code">funcall <span class="paren4">(<span class="code">audio-buffer-filler chip</span>)</span>
buffer rate angle</span>)</span></span>)</span>
<span class="paren2">(<span class="code">portaudio:write-stream audio-stream buffer</span>)</span></span>)</span>
<span class="paren1">(<span class="code">sleep +audio-buffer-time+</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>We'll also need a way to actually pause and unpause the debugger. We could do
it through NREPL or SLIME, but it'll be easier if we just add a key for it over
on the Qt side of things:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-override</span></i> <span class="paren2">(<span class="code">screen key-release-event</span>)</span> <span class="paren2">(<span class="code">ev</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">key <span class="paren5">(<span class="code">q+:key ev</span>)</span></span>)</span>
<span class="paren4">(<span class="code">pad-key <span class="paren5">(<span class="code">pad-key-for key</span>)</span></span>)</span>
<span class="paren4">(<span class="code">debugger <span class="paren5">(<span class="code">chip8::chip-debugger chip</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> pad-key
<span class="paren4">(<span class="code">chip8::keyup chip pad-key</span>)</span>
<span class="paren4">(<span class="code">qtenumcase key
<span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_escape</span>)</span> <span class="paren6">(<span class="code">die screen</span>)</span></span>)</span>
<span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_f1</span>)</span> <span class="paren6">(<span class="code">chip8::reset chip</span>)</span></span>)</span>
<span class="comment">; NEW
</span> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_space</span>)</span> <span class="paren6">(<span class="code">chip8::debugger-toggle-pause debugger</span>)</span>
<span class="paren6">(<span class="code">t <span class="paren1">(<span class="code">pr <span class="keyword">:unknown-key</span> <span class="paren2">(<span class="code">format nil <span class="string">&quot;~X&quot;</span> key</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">stop-overriding</span>)</span></span>)</span></span></span></span></code></pre>
<p>Now we can pause and unpause the emulator with the space bar, which is a handy
feature to have all on its own.</p>
<h2 id="s4-stepping"><a href="index.html#s4-stepping">Stepping</a></h2>
<p>The next step is adding support for single-stepping through the code. We'll
start by adding a slot to the <code>debugger</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> debugger
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">take-step nil <span class="keyword">:type</span> boolean</span>)</span></span>)</span></span></code></pre>
<p>Then we'll add an API function to set it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-step <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">debugger-take-step debugger</span>)</span> t</span>)</span></span>)</span></span></code></pre>
<p>Now we need to wire this into the emulator, which is a bit tricky. We need to
handle the following cases:</p>
<ul>
<li>The debugger isn't paused at all, just run normally.</li>
<li>The debugger is paused, but <code>take-step</code> is false, so just wait.</li>
<li>The debugger is paused and <code>take-step</code> is true, so take a single step and then
pause after that.</li>
</ul>
<p>We'll encapsulate this in a separate API function with a decidedly mediocre
name. <code>debugger-check-wait</code> will be run before each cycle, and will return <code>t</code>
whenever execution should wait. It will handle checking and updating
<code>take-step</code> as needed:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-check-wait <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="string">&quot;Return `t` if the debugger wants execution to wait, `nil` otherwise.&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="comment">;; If we're not paused, just run normally.
</span> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">not paused</span>)</span> nil</span>)</span>
<span class="comment">;; If we're paused, but are ready to step, run just this once.
</span> <span class="paren4">(<span class="code">take-step <span class="paren5">(<span class="code">setf take-step nil</span>)</span>
nil</span>)</span>
<span class="comment">;; Otherwise we're fully paused, so wait.
</span> <span class="paren4">(<span class="code">t t</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>I didn't name it with a <code>-p</code> suffix because although it qualifies as a predicate
according to the Common Lisp spec (&quot;a function that returns a generalized
boolean as its first value&quot;) it has side effects, and I generally don't expect
predicates to modify state.</p>
<p>We can wire this into <code>emulate-cycle</code>, replacing the vanilla
<code>debugger-paused-p</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> emulate-cycle <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">debugger-check-wait debugger</span>)</span> <span class="comment">; NEW
</span> <span class="paren4">(<span class="code">sleep 10/1000</span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">instruction <span class="paren1">(<span class="code">cat-bytes
<span class="paren2">(<span class="code">aref memory program-counter</span>)</span>
<span class="paren2">(<span class="code">aref memory <span class="paren3">(<span class="code">1+ program-counter</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">zapf program-counter <span class="paren6">(<span class="code">chop 12 <span class="paren1">(<span class="code">+ % 2</span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">dispatch-instruction chip instruction</span>)</span></span>)</span></span>)</span>
nil</span>)</span></span>)</span></span></code></pre>
<p>Now pausing will work as before, but whenever we set <code>take-step</code> to <code>t</code> the CPU
will emulate one more cycle before going back to being paused. We can add a key
to the Qt screen to request a step:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-override</span></i> <span class="paren2">(<span class="code">screen key-release-event</span>)</span> <span class="paren2">(<span class="code">ev</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">key <span class="paren5">(<span class="code">q+:key ev</span>)</span></span>)</span>
<span class="paren4">(<span class="code">pad-key <span class="paren5">(<span class="code">pad-key-for key</span>)</span></span>)</span>
<span class="paren4">(<span class="code">debugger <span class="paren5">(<span class="code">chip8::chip-debugger chip</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> pad-key
<span class="paren4">(<span class="code">chip8::keyup chip pad-key</span>)</span>
<span class="paren4">(<span class="code">qtenumcase key
<span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_escape</span>)</span> <span class="paren6">(<span class="code">die screen</span>)</span></span>)</span>
<span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_f1</span>)</span> <span class="paren6">(<span class="code">chip8::reset chip</span>)</span></span>)</span>
<span class="comment">; NEW
</span> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_f7</span>)</span> <span class="paren6">(<span class="code">chip8::debugger-step debugger</span>)</span>
<span class="paren6">(<span class="code"><span class="paren1">(<span class="code">q+:qt.key_space</span>)</span> <span class="paren1">(<span class="code">chip8::debugger-toggle-pause debugger</span>)</span>
<span class="paren1">(<span class="code">t <span class="paren2">(<span class="code">pr <span class="keyword">:unknown-key</span> <span class="paren3">(<span class="code">format nil <span class="string">&quot;~X&quot;</span> key</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren4">(<span class="code">stop-overriding</span>)</span></span>)</span></span></span></span></span></span></code></pre>
<p>Now we can walk through the code one instruction at a time by pausing the
emulator and hitting <code>F7</code> to take a step. I picked <code>F7</code> because it matches the
&quot;take step&quot; key for another emulator I use.</p>
<p>This is all great, but pretty useless unless we can also see what instruction is
about to run.</p>
<h2 id="s5-printing"><a href="index.html#s5-printing">Printing</a></h2>
<p>Handling the printing is going to be ugly. We want to print the disassembly of
the current instruction whenever we &quot;arrive&quot; at a new instruction. This will
happen:</p>
<ul>
<li>When we first pause the debugger.</li>
<li>After a single step has been taken.</li>
</ul>
<p>We'll try to keep things as clean as possible on the emulator side of things by
containing all the ugliness in a <code>debugger-arrive</code> function, which we'll call at
the start of every possible cycle:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> emulate-cycle <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">debugger-arrive debugger chip</span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">debugger-check-wait debugger program-counter</span>)</span>
<span class="paren4">(<span class="code">sleep 10/1000</span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">instruction <span class="paren1">(<span class="code">cat-bytes
<span class="paren2">(<span class="code">aref memory program-counter</span>)</span>
<span class="paren2">(<span class="code">aref memory <span class="paren3">(<span class="code">1+ program-counter</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">zapf program-counter <span class="paren6">(<span class="code">chop 12 <span class="paren1">(<span class="code">+ % 2</span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">dispatch-instruction chip instruction</span>)</span></span>)</span></span>)</span>
nil</span>)</span></span>)</span></span></code></pre>
<p>Now we can handle the ugliness on the debugger side. We'll add a slot for
tracking when we're waiting to arrive:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> debugger
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">awaiting-arrival nil <span class="keyword">:type</span> boolean</span>)</span></span>)</span></span></code></pre>
<p>Then we'll update our API to set this flag properly:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-pause <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code">setf paused t
awaiting-arrival t</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-unpause <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code">setf paused nil
awaiting-arrival nil</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-check-wait <span class="paren2">(<span class="code">debugger</span>)</span>
<span class="string">&quot;Return `t` if the debugger wants execution to wait, `nil` otherwise.&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="comment">;; If we're not paused, just run normally.
</span> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">not paused</span>)</span> nil</span>)</span>
<span class="comment">;; If we're paused, but are ready to step, run just this once.
</span> <span class="paren4">(<span class="code">take-step <span class="paren5">(<span class="code">setf take-step nil
awaiting-arrival t</span>)</span> <span class="comment">; NEW
</span> nil</span>)</span>
<span class="comment">;; Otherwise we're fully paused, so wait.
</span> <span class="paren4">(<span class="code">t t</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>We didn't have to touch <code>debugger-toggle-pause</code> because it uses the lower-level
API functions, so everything works properly.</p>
<p>Now we can implement <code>debugger-arrive</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-arrive <span class="paren2">(<span class="code">debugger chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code">when awaiting-arrival
<span class="paren4">(<span class="code">setf awaiting-arrival nil</span>)</span>
<span class="paren4">(<span class="code">debugger-print debugger chip</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Finally we can implement the actual instruction-printing function, which is
trivial thanks to the work we did in the previous post:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-print <span class="paren2">(<span class="code">debugger chip</span>)</span>
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">ignore debugger</span>)</span></span>)</span>
<span class="paren2">(<span class="code">print-disassembled-instruction <span class="paren3">(<span class="code">chip-memory chip</span>)</span>
<span class="paren3">(<span class="code">chip-program-counter chip</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Now we can run the emulator, press <code>space</code> to pause and we'll see the current
instruction (the one about to be executed) dumped to the console. We can press
<code>F7</code> to step one instruction at a time and they'll each be dumped in turn:</p>
<p><a href="http://stevelosh.com/static/images/blog/2017/01/chip8-step.png"><img src="http://stevelosh.com/static/images/blog/2017/01/chip8-step.png" alt="Screenshot of CHIP-8 stepping"></a></p>
<h2 id="s6-breakpoints"><a href="index.html#s6-breakpoints">Breakpoints</a></h2>
<p>Now that we can pause and step we can start tracking down bugs in the emulator.
We'll want to add breakpoints so that we don't need to manually step until we
get to a problematic instruction.</p>
<p>We'll start by adding a slot to store the breakpoint addresses, as well as API
functions for adding and removing them:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> debugger
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">breakpoints nil <span class="keyword">:type</span> list</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-add-breakpoint <span class="paren2">(<span class="code">debugger address</span>)</span>
<span class="paren2">(<span class="code">pushnew address <span class="paren3">(<span class="code">debugger-breakpoints debugger</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-remove-breakpoint <span class="paren2">(<span class="code">debugger address</span>)</span>
<span class="paren2">(<span class="code">removef <span class="paren3">(<span class="code">debugger-breakpoints debugger</span>)</span> address</span>)</span></span>)</span></span></code></pre>
<p><code>removef</code> is from Alexandria and is just a <code>modify-macro</code> for <code>remove</code>.</p>
<p>Next we'll create a <code>debugger-check-breakpoints</code> function that will check
whether we're at a breakpoint, and pause the debugger if so. It will return <code>t</code>
if we're at a breakpoint:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-check-breakpoints <span class="paren2">(<span class="code">debugger address</span>)</span>
<span class="string">&quot;Return `t` if the debugger is at a breakpoint, `nil` otherwise.&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren3">(<span class="code">member address <span class="paren4">(<span class="code">debugger-breakpoints debugger</span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">progn</span></i> <span class="paren4">(<span class="code">debugger-pause debugger</span>)</span>
t</span>)</span>
nil</span>)</span></span>)</span></span></code></pre>
<p>Note that we use the <code>debugger-pause</code> API function here to make sure we handle
setting the <code>paused</code> and <code>awaiting-arrival</code> slots properly. Now we can modify
<code>debugger-check-wait</code> to use this function. We'll also need to update it to
take the current instruction's address:</p>
<pre><code><span class="code"> <span class="comment">; NEW
</span><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> debugger-check-wait <span class="paren2">(<span class="code">debugger address</span>)</span>
<span class="string">&quot;Return `t` if the debugger wants execution to wait, `nil` otherwise.&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-debugger</span></i> <span class="paren3">(<span class="code">debugger</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="comment">;; If we're not paused, we might be at a breakpoint. ; NEW
</span> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">not paused</span>)</span> <span class="paren5">(<span class="code">debugger-check-breakpoints debugger <span class="comment">; NEW
</span> address</span>)</span></span>)</span> <span class="comment">; NEW
</span>
<span class="comment">;; If we're paused, but are ready to step, run just this once.
</span> <span class="paren4">(<span class="code">take-step <span class="paren5">(<span class="code">setf take-step nil
awaiting-arrival t</span>)</span>
nil</span>)</span>
<span class="comment">;; Otherwise we're fully paused, so wait.
</span> <span class="paren4">(<span class="code">t t</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>And that's it, nothing else needs to change. We can add breakpoints to the
currently running <code>chip</code> in the REPL with something like
<code>(debugger-add-breakpoint (chip-debugger *c*) #x200)</code>. This isn't the nicest
interface, but it'll do the job for now.</p>
<h2 id="s7-result"><a href="index.html#s7-result">Result</a></h2>
<p>We've implemented basic pausing, single-stepping, and breakpoints. There are
a lot more features we could add to the debugger, and we'll cover some in later
posts.</p>
<p>Creating a debugging interface isn't the most glamorous work, but it pays for
itself the first time you run into a bug in the emulator.</p>
<p>I'm not entirely happy with all the coupling between the emulator and the
debugger, but I'm not sure it's possible to completely untangle the two. If
you've got suggestions for how to design the interface more cleanly please let
me know.</p>
<h2 id="s8-future"><a href="index.html#s8-future">Future</a></h2>
<p>We're nearing the end of this series, but there are a couple more things I'll
cover before it's over:</p>
<ul>
<li>A graphical interface to the debugger and disassembler.</li>
<li>Polishing up the interface a bit with menus for loading ROMs, configuring
options, etc.</li>
</ul>
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a><a href='https://twitter.com/stevelosh/'>Twitter</a><a href='https://instagram.com/thirtytwobirds/'>Instagram</a><a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>