Start writing debugger entry
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
+```