421 lines
No EOL
36 KiB
HTML
421 lines
No EOL
36 KiB
HTML
<!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">"~X"</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">"Return `t` if the debugger wants execution to wait, `nil` otherwise."</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 ("a function that returns a generalized
|
|
boolean as its first value") 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">"~X"</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
|
|
"take step" 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 "arrive" 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">"Return `t` if the debugger wants execution to wait, `nil` otherwise."</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">"Return `t` if the debugger is at a breakpoint, `nil` otherwise."</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">"Return `t` if the debugger wants execution to wait, `nil` otherwise."</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> |