(local command (require :core.command)) (local {: spawn : kill } (require :lib.spawn)) (local socket (require :socket)) (local json (require :lib.dkjson)) (local asm (require :asm.asm)) (local lume (require :lib.lume)) (local gsplus-path "/home/jeremy/src/gsplus/result/bin/GSplus") (local debug-port 8769) (local reg-write-format { :PC " k%06X" :A " a%04X" :X " x%04X" :Y " y%04X" :S " s%04X" :D " d%04X" :B " b%02X" :PSR " p%06X" }) (fn get-cpu-reg [response] (-> response (lume.filter #(= $1.type :cpu)) (. 1 :data))) (local machine {:boot (fn [self] (when (not self.pid) (set self.pid (spawn [:nixGL gsplus-path :-debugport (tostring debug-port)])))) :run (fn [self] (self:boot) (while (not self.socket) (self:connect)) (self:hello)) :die (fn [self] (self:disconnect) (when self.pid (kill (- self.pid) 1) (set self.pid nil))) :connect (fn [self] (when (not self.socket) (set self.socket (socket.connect :localhost debug-port)) (if self.socket (self.socket:settimeout 1) (love.timer.sleep 0.25)))) :disconnect (fn [self] (when self.socket (self.socket:close) (set self.socket nil))) :connected? (fn [self] self.socket) :running? (fn [self] self.pid) :cmd (fn [self cmd] (self.socket:send (.. cmd "\n"))) :response (fn [self] (var bytes "") (var done false) (while (not done) (local (line err) (self.socket:receive)) (set done (or (= line "") (= line nil))) (when line (set bytes (.. bytes line)))) (json.decode bytes)) :cmd-response (fn [self cmd] (self:cmd cmd) (self:response)) :hello (fn [self] (self:cmd-response "1")) :continue (fn [self] (self:cmd "3")) :step (fn [self] (self:cmd-response "2")) :getreg (fn [self] (get-cpu-reg (self:cmd-response "4"))) :set-bp (fn [self addr] (self:cmd-response (.. "8" (string.format "%06X" addr)))) :delete-bp (fn [self addr] (self:cmd-response (.. "9" (string.format "%06X" addr)))) :get-bp (fn [self] (self:cmd-response "A")) :write (fn [self addr bytes] (var bytes-to-write bytes) (var addrout addr) (while (> (length bytes-to-write) 0) (local bytesout (bytes-to-write:sub 1 50)) (self:cmd-response (.. "7" (string.format "%06X" addrout) (bytesout:tohex))) (set bytes-to-write (bytes-to-write:sub 51)) (set addrout (+ addrout 50)))) :setreg (fn [self regvals] (var bytes "5") (each [reg val (pairs regvals)] (set bytes (.. bytes (string.format (. reg-write-format reg) val)))) (self:cmd-response bytes)) :stop-at (fn [self addr k] (local fulladdr (bit.bor addr (bit.lshift (or k 0) 16))) (self:set-bp fulladdr) ; wait for breakpoint to be hit (var bp-response (self:response)) (self:delete-bp fulladdr) (when (not bp-response) ; attempt to consume extra response in case the breakpoint was actually hit while sending the message to delete the breakpoint (set bp-response (self:response))) (when bp-response (local reg (get-cpu-reg bp-response)) (local pc (tonumber reg.PC 16)) (local curr-k (tonumber reg.K 16)) (and (= pc addr) (= curr-k (or k 0))))) :jump (fn [self addr] (self:setreg {:PC addr})) :do (fn [self prg f] (when (self:stop-at (prg:lookup-addr :debug-stub)) (f self) (self:continue) true)) :hotswap (fn [self prg-old prg-new] (self:do prg-old (fn [] (prg-new:upload self) ; on-hotswap may move around in memory; we can handle this (self:jump (prg-new:lookup-addr :on-hotswap))))) :stub (fn [self org post-check-jump ...] (org:append :debug-stub [:jmp post-check-jump] :on-hotswap ...)) }) (command.add (fn [] machine.socket) { "gsplus:hello" #(machine:hello) "gsplus:dump-cpu-state" #(pp (machine:getreg)) "gsplus:step" #(pp (machine:step)) "gsplus:continue" #(pp (machine:continue)) }) machine