ef47c27c6470

Start writing debugger entry
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Mon, 02 Jan 2017 12:55:34 +0000
parents fa42e28f21e5
children e7a83e2baf52
branches/tags (none)
files content/blog/2016/12/chip8-debugging-infrastructure.markdown

Changes

--- a/content/blog/2016/12/chip8-debugging-infrastructure.markdown	Wed Dec 28 12:50:19 2016 -0500
+++ b/content/blog/2016/12/chip8-debugging-infrastructure.markdown	Mon Jan 02 12:55:34 2017 +0000
@@ -30,10 +30,150 @@
 
 ## Architecture
 
+The overall goal will be to keep the debugging infrastructure as separate from
+possible the rest of the emulation code.  Unfortunately the nature of debugging
+will require us to weave it into some of the normal emulator code, but we'll try
+to keep the pollution to a minimum.
+
+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 `chip` struct will have
+a `debugger` slot and will use the debugger API to interact with it.  Later on,
+the graphical debugger UI will also use this API.
+
 ## The Debugger Data Structure
 
+We'll start by creating a `debugger` struct and a `with-debugger` macro for it.
+We'll be adding fields to this struct as we build the debugging infrastructure.
+
+```lisp
+(defstruct debugger
+  ; ...
+  )
+
+(define-with-macro debugger
+  ; ...
+  )
+```
+
+We'll then add a `debugger` slot to our `chip` struct:
+
+```lisp
+(defstruct chip
+  ; ...
+  (debugger (make-debugger) :type debugger :read-only t))
+```
+
 ## Pausing
 
+The first thing we'll add is support for pausing and unpausing execution.  We'll
+add a `paused` slot to the debugger:
+
+```lisp
+(defstruct debugger
+  (paused nil :type boolean)
+  ; ...
+  )
+```
+
+Then we'll add some simple API functions so the emulator won't have to directly
+work with the debugger's slots:
+
+```lisp
+(defun debugger-pause (debugger)
+  (with-debugger (debugger)
+    (setf paused t print-needed t)))
+
+(defun debugger-unpause (debugger)
+  (with-debugger (debugger)
+    (setf paused nil print-needed nil)))
+
+(defun debugger-toggle-pause (debugger)
+  (if (debugger-paused debugger)
+    (debugger-unpause debugger)
+    (debugger-pause debugger)))
+
+(defun debugger-paused-p (debugger)
+  (debugger-paused debugger))
+```
+
+Now we'll modify `emulate-cycle` to check if the debugger is paused before
+actually running an instruction:
+
+```lisp
+(defun emulate-cycle (chip)
+  (with-chip (chip)
+    (if (debugger-paused-p debugger)      ; NEW
+      (sleep 10/1000)                     ; NEW
+      (let ((instruction (cat-bytes
+                           (aref memory program-counter)
+                           (aref memory (1+ program-counter)))))
+        (zapf program-counter (chop 12 (+ % 2)))
+        (dispatch-instruction chip instruction)))
+    nil))
+```
+
+The timer thread will need to skip the timer decrements whenever the debugger is
+paused:
+
+```lisp
+(defun run-timers (chip)
+  (with-chip (chip)
+    (iterate
+      (while running)
+      (when (not (debugger-paused-p debugger)) ; NEW
+        (decrement-timers chip))
+      (sleep 1/60))))
+```
+
+Next we'll modify the sound thread to be silent when paused, otherwise pausing
+during a buzz would result in perpetual buzzing:
+
+```lisp
+(defun run-sound (chip)
+  (portaudio:with-audio
+    (portaudio:with-default-audio-stream
+        (audio-stream 0 1
+                      :sample-format :float
+                      :sample-rate +sample-rate+
+                      :frames-per-buffer +audio-buffer-size+)
+      (with-chip (chip)
+        (iterate (with buffer = (make-audio-buffer))
+                 (with angle = 0.0)
+                 (with rate = (audio-rate 440))
+                 (while running)
+                 (if (and (plusp sound-timer)
+                          (not (debugger-paused-p debugger)))  ; NEW
+                   (progn
+                     (setf angle (funcall (audio-buffer-filler chip)
+                                          buffer rate angle))
+                     (portaudio:write-stream audio-stream buffer))
+                   (sleep +audio-buffer-time+))))))
+  nil)
+```
+
+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 much easier if we just add a key for it
+over on the Qt side of things:
+
+```lisp
+(define-override (screen key-release-event) (ev)
+  (let* ((key (q+:key ev))
+         (pad-key (pad-key-for key)))
+    (if pad-key
+      (chip8::keyup chip pad-key)
+      (qtenumcase key
+        ((q+:qt.key_escape) (die screen))
+        ((q+:qt.key_f1)     (chip8::reset chip))
+        ((q+:qt.key_space)                                     ; NEW
+         (chip8::debugger-toggle-pause (chip8::debugger chip)) ; NEW
+        (t (pr :unknown-key (format nil "~X" key))))))
+  (stop-overriding))
+```
+
+Now we can pause and unpause the emulator with the space bar, which is a handy
+feature to have all on its own.
+
 ## Stepping
 
 ## Printing
@@ -45,3 +185,22 @@
 ## Result
 
 ## Future
+
+
+```lisp
+```
+
+```lisp
+```
+
+```lisp
+```
+
+```lisp
+```
+
+```lisp
+```
+
+```lisp
+```