# HG changeset patch # User Steve Losh # Date 1483361734 0 # Node ID ef47c27c647091f29f7f0f0741370b1ee4181139 # Parent fa42e28f21e50a219a9446808156a80241fe99ea Start writing debugger entry diff -r fa42e28f21e5 -r ef47c27c6470 content/blog/2016/12/chip8-debugging-infrastructure.markdown --- 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 +```