#!/bin/sl -i (doc-group lsd "Debugging functionality.") (defstruct reg "A register of the processor. All registers are exposed as top-level symbols. The fields are internal. The top-level symbol `registers` is a list of all available registers. To read the value of a register use `reg-read` Examples: (reg-read AX)" :doc-group lsd :doc-see reg-read :doc-see registers :constructor (mk-reg name type addr size) name type addr size) (defstruct symbol "A symbol of the process. Name is a string denoting the symbol, type is a character as described in a.out(6), and address is the location of the symbol in the process address space" :doc-group lsd :constructor (mk-symbol name type addr) name type addr) (defstruct global "All the global symbols, separated into text and data symbols. The text and data fields are both tables from strings to symbols." :doc-group lsd :doc-see symbol text data) (defstruct frame "A stack frame. Sym is the enclosing function symbol and instruction address of the frame. Retpc is the return instruction address. Sp is the stack pointer value. Locals are all the local symbols." :doc-group lsd :doc-see symbol :constructor (mk-frame sym retpc sp locals) sym retpc sp locals) (def tracers (table "386" (λ () (lsd-ctrace (curPC) (reg-read SP) (u64 0))) "68020" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read A7))) "amd64" (λ () (lsd-ctrace (curPC) (reg-read SP) (u64 0))) "arm" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read R14))) "arm64" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read R30))) "mips" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read R31))) "power" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read LR))) "power64" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read LR))))) (def registers NIL)(def bpinst NIL)(def globals NIL)(def coref NIL)(def regsf NIL)(def fpregsf NIL)(def proc-stdin NIL)(def pids NIL)(def pid -1)(def bptbl (table))(def ctrace (get tracers (os-getenv "objtype"))) (def (procfile s . flags) (when (< pid 0) (error "no active process")) (let {[path (str "/proc/" pid "/" s)]} (apply file (cons path flags)))) (def (writectl msg) (let {[ctlf (procfile "ctl" :write)]} (io-write ctlf msg) (io-close ctlf))) (def (exited) (when (< pid 0) (error "no active pid")) (princ "process " pid " exited\n") (set! pids (cdr pids) pid (if pids (car pids) -1) bptbl (table)) (detach)) (def (readnote) (trycatch (let {[notef (procfile "note" :read)]} (prog1 (io-read-all notef) (io-close notef))) (λ (e) (if (and (eq? (car e) 'io-error) (= (str-find (cadr e) "could not open") 0)) (exited) (raise e))))) (def (start) (writectl "start"))(def (startstop) (writectl "startstop") (readnote))(def (stop) (writectl "stop") (readnote))(def (waitstop) (unless (eq? (status) 'Stopped) (princ "Waiting... " status "\n") (stop))) (def (follow addr) "Return a list of the next possible executing instructions." :doc-group lsd (lsd-follow addr)) (defmacro (io-pread f off . rest) `(begin (io-pos! ,f ,off) (io-read ,f ,.rest))) (defmacro (io-pwrite f off . rest) `(begin (io-pos! ,f ,off) (io-write ,f ,.rest) (io-flush ,f))) (defmacro (core-read addr . rest) `(begin (unless coref (error "not attached to proc")) (io-pread coref ,addr ,.rest))) (defmacro (core-write addr . rest) `(begin (unless coref (error "not attached to proc")) (io-pwrite coref ,addr ,.rest))) (def (reg-read reg) "Read the value of a register. Examples: (reg-read AX) ; read the return register on amd64 (reg-read PC) ; read the current instruction address." :doc-group lsd :doc-see reg :doc-see registers (unless regsf (error "not attached to proc")) (let {[f (case (reg-type reg) ((:gpreg) regsf) ((:fpreg) fpregsf))]} (io-pread f (reg-addr reg) (reg-size reg)))) (defmacro (symbol-read symbol . rest) "Read the value from the core file at the symbol's address." :doc-group lsd :doc-see local-symbol :doc-see global-symbol `(core-read (symbol-addr ,symbol) ,.rest)) (defmacro (defcval name size) (let {[sizesym `(sym ,size)]} `(def (,name loc (n NIL)) ,(str "Read the " size " value at the given location\n\n" " Optionally read an array of n values\n\n" " The input to this function is as in @.") :doc-see @ :doc-group lsd (if n (core-read (@ loc) ,sizesym n) (core-read (@ loc) ,sizesym))))) (defcval c-byte u8)(defcval c-char utf8)(defcval c-uchar u8)(defcval c-short s16)(defcval c-ushort u16)(defcval c-int s32)(defcval c-uint u32)(defcval c-long s32)(defcval c-ulong u32)(defcval c-vlong s64)(defcval c-uvlong u64)(defcval c-ptr ptr)(defcval c-float f32)(defcval c-double f64) (def (c-str loc (n NIL)) «Read the null-terminated strings at the pointer location. Optionally read a vector of n strings. The input to this function is as in @. Examples: Read all the arguments to the program. (c-str (c-ptr "argv") (c-int "argc")) Read a value `s` of type `char*`. (c-str "s")» :doc-see @ :doc-group lsd (def (readstr loc) (unless coref (error "not attached to proc")) (io-pos! coref loc) (str-trim (io-read-until coref 0) NIL (λ (r) (eq? r #\nul)))) (if n (map 'vec readstr (c-ptr (@ loc) n)) (readstr (c-ptr (@ loc))))) (def (@ loc) «Return the address of local or global data. The input to this function can be: 1. A string that refers to a local or global symbol of the program. 2. A number which is the address itself, 3. A symbol in which case the symbol's address is used. Examples: (@ "argc") ; ↳ #p64(0x7fffffffeed0)» :doc-group lsd (def (symb-addr s) (let {[type (symbol-type s)]} (or (eq? type #\a) (eq? type #\p) (eq? type #\d) (eq? type #\D) (eq? type #\b) (eq? type #\B) (error "expected a data symbol: " s))) (symbol-addr s)) (def (str-addr s) (symb-addr (or (local-symbol s) (global-symbol s :data T) (error "could not find symbol " s)))) (cond ((str? loc) (str-addr loc)) ((num? loc) (ptr loc)) ((symbol? loc) (symb-addr loc)) (else (error "str|num|symbol")))) (def (pc loc) «Return the program instruction address corresponding to a location. A location can be: 1. A string that refers to a symbol (such as a function) in the process's symbol table, 2. A string of the form "file:line" used to find a program address corresponding to given line of source code, 3. A number which is the address itself, 4. A symbol in which case the symbol's address is used. Examples: ; look up a symbol (pc "strecpy") ; ↳ #p64(0x22be91) ; locate source code address (pc "/sys/src/cmd/ls.c:75") ; ↳ #p64(0x20009f)» :doc-see filepc :doc-see symbol :doc-group lsd (def (symb-addr s) (let {[type (symbol-type s)]} (or (eq? type #\t) (eq? type #\T) (error "expected a text symbol: " s))) (symbol-addr s)) (def (str-addr s) (trycatch (filepc s) (λ (e) (when (eq? (car e) 'io-error) (raise e)) (let {[symb (global-symbol s :text T)]} (if symb (symb-addr symb) (error "could not find symbol " s)))))) (cond ((str? loc) (str-addr loc)) ((num? loc) (ptr loc)) ((symbol? loc) (symb-addr loc)) (else (error "str|num|symbol")))) (def (bpsave a) (core-read a 'u8 (size-of bpinst))) (def (bpset at) "Set a breakpoint at the location. Location is as in pc" :doc-see pc :doc-see bpdel :doc-group lsd (waitstop) (let {[addr (pc at)]} (when (has? bptbl addr) (error "breakpoint already set at " at)) (put! bptbl addr (bpsave addr)) (core-write addr bpinst))) (def (bpdel at) "Delete a breakpoint at the location. Location is as in pc" :doc-see pc :doc-see bpset :doc-group lsd (waitstop) (let {[addr (pc at)]} (unless (has? bptbl addr) (error "breakpoint not set at " at)) (core-write addr (get bptbl addr)) (del! bptbl addr))) (def (wpset at size . rest) «Set a watchpoint starting at the location. A breakpoint is emitted for memory access from `at` up to `at+size` bytes. The default access type is to watch for both reads and writes. Location is as in @ Bugs: Only works on 386/amd64. You can only set one watchpoint at a time. Setting a watchpoint clears other watchpoints. Examples: (wpset "anint" 4 :write) ; watch for writes to an integer. (wpset "avlong" 8 :read) ; watch for reads (and writes) to 8 bytes. (wpset "avlong" 8) ; same as previous example.» :doc-see @ :doc-see wpdel :doc-group lsd (waitstop) (or (eq? size 1) (eq? size 2) (eq? size 4) (eq? size 8) (arg-error "invalid size, must be 1, 2, 4 or 8, got: " size)) (def (wptype rest) (let {[type (arr 'rune #\r #\w #\-)] [readset NIL]} (for-each (λ (atype) (case atype (:read (set! readset T type (arr 'rune #\r #\w #\-))) (:write (unless readset (aset! type 0 #\-))) (else (arg-error "invalid watchpoint type " atype)))) rest) type)) (let {[wpf (procfile "watchpt" :write)]} (io-write wpf (str (wptype rest) " " (fixnum (@ at)) " " size "\n")) (io-close wpf))) (def (wpdel) "Deletes all watchpoints." :doc-see wpset :doc-group lsd (waitstop) (let {[wpf (procfile "watchpt" :write)]} (io-write wpf "\n") (io-close wpf))) (def (detach) (when regsf (io-close regsf)) (when fpregsf (io-close fpregsf)) (when coref (io-close coref)) (void)) (def (attach) (detach) (set! regsf (procfile "regs" :read :write) fpregsf (procfile "fpregs" :read :write) coref (procfile "mem" :read :write) pids (cons pid pids)) (void)) (def (status) "Return the running status of the process." :doc-group lsd (let {[sf (procfile "status")]} (prog1 (caddr (read-all sf)) (io-close sf)))) (def (_stk (:level 0) (:n NIL)) "Return the call stack in the form of a list of stack frames. Optionally specify what level of the stack to return, starting at the innermost level and how many levels to display." :doc-group lsd :doc-see frame (let {[s (nthcdr (reverse! (ctrace)) level)]} (if n (subseq s 0 n) s))) (def (stk (:level 0) (:n NIL) (:locals NIL)) "Print the stack trace optionally showing local auto values. Bugs: Prints the values of parameters and autos as unsigned 64 bit integers, not as their actual types." :doc-group lsd :doc-see _stk :doc-see lstk (def (go pc fs) (if (not fs) pc (let* {[f (car fs)] [fsym (frame-sym f)]} (receive (params autos) (partition (λ (x) (equal? #\p (symbol-type x))) (frame-locals f)) (set! params (sort params #.< :key symbol-addr) autos (sort autos #.> :key symbol-addr)) (princ (symbol-name fsym) "(") (princ (str-join (map (λ (p) (str (symbol-name p) "=" (symbol-read p (sym ptr)))) params) ", ")) (princ ")+" (ptr (- pc (symbol-addr fsym))) " ") (princ (src pc) "\n") (when locals (for-each (λ (l) (princ " " (symbol-name l) "=" (symbol-read l (sym ptr)) "\n")) autos)) (go (frame-retpc f) (cdr fs)))))) (let* {[tos (go (curPC) (_stk :level level :n n))] [topsym (lsd-findsym tos)]} (princ (symbol-name topsym) "+" (ptr (- tos (symbol-addr topsym))) " " (src tos) "\n"))) (def (lstk (:level 0) (:n NIL)) "Print the stack trace showing local auto values." :doc-group lsd :doc-see _stk :doc-see stk (stk :locals T :level level :n n)) (def (curPC) (if (>= pid 0) (ptr (reg-read PC)) (error "No active process"))) (def (step (n 1)) "Step `n` assembly instructions. Return the next instruction address to be executed or `NIL` if the program has exited." :doc-group lsd (if (= n 0) (curPC) (let* {[addr (curPC)] [on-bp (has? bptbl addr)]} (when on-bp (core-write addr (get bptbl addr))) (let* {[fs (follow addr)] [os (map bpsave fs)]} (for-each (λ (f) (core-write f bpinst)) fs) (startstop) (for-each (λ (f o) (core-write f o)) fs os) (when on-bp (core-write addr bpinst)) (step (1- n)))))) (def (cont (:print T)) "Continue program execution. Return the next instruction address to be executed or `void` if the program has exited. Optionally print any notes that may have caused the program to stop." :doc-group lsd (when (has? bptbl (curPC)) (step)) (let {[note (startstop)]} (and print (not (void? note)) (princ note "\n"))) (trycatch (curPC) (λ (_) (void)))) (def (func) "Continue program execution until the current function returns." :doc-group lsd (let* {[bp (frame-retpc (car (_stk)))] [o (bpsave bp)]} (core-write bp bpinst) (cont :print NIL) (core-write bp o)) (curPC)) (def (line) "Step one line of the source code. Note: This will step into functions not over." :doc-group lsd (let {[orig (src)]} (def (go) (step) (if (not (equal? orig (src))) (curPC) (go)))Lines 1–500 of 702. Load more
#!/bin/sl -i (doc-group lsd "Debugging functionality.") (defstruct reg "A register of the processor. All registers are exposed as top-level symbols. The fields are internal. The top-level symbol `registers` is a list of all available registers. To read the value of a register use `reg-read` Examples: (reg-read AX)" :doc-group lsd :doc-see reg-read :doc-see registers :constructor (mk-reg name type addr size) name type addr size) (defstruct symbol "A symbol of the process. Name is a string denoting the symbol, type is a character as described in a.out(6), and address is the location of the symbol in the process address space" :doc-group lsd :constructor (mk-symbol name type addr) name type addr) (defstruct global "All the global symbols, separated into text and data symbols. The text and data fields are both tables from strings to symbols." :doc-group lsd :doc-see symbol text data) (defstruct frame "A stack frame. Sym is the enclosing function symbol and instruction address of the frame. Retpc is the return instruction address. Sp is the stack pointer value. Locals are all the local symbols." :doc-group lsd :doc-see symbol :constructor (mk-frame sym retpc sp locals) sym retpc sp locals) (def tracers (table "386" (λ () (lsd-ctrace (curPC) (reg-read SP) (u64 0))) "68020" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read A7))) "amd64" (λ () (lsd-ctrace (curPC) (reg-read SP) (u64 0))) "arm" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read R14))) "arm64" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read R30))) "mips" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read R31))) "power" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read LR))) "power64" (λ () (lsd-ctrace (curPC) (reg-read SP) (reg-read LR))))) (def registers NIL) (def bpinst NIL) (def globals NIL) (def coref NIL) (def regsf NIL) (def fpregsf NIL) (def proc-stdin NIL) (def pids NIL) (def pid -1) (def bptbl (table)) (def ctrace (get tracers (os-getenv "objtype"))) (def (procfile s . flags) (when (< pid 0) (error "no active process")) (let {[path (str "/proc/" pid "/" s)]} (apply file (cons path flags)))) (def (writectl msg) (let {[ctlf (procfile "ctl" :write)]} (io-write ctlf msg) (io-close ctlf))) (def (exited) (when (< pid 0) (error "no active pid")) (princ "process " pid " exited\n") (set! pids (cdr pids) pid (if pids (car pids) -1) bptbl (table)) (detach)) (def (readnote) (trycatch (let {[notef (procfile "note" :read)]} (prog1 (io-read-all notef) (io-close notef))) (λ (e) (if (and (eq? (car e) 'io-error) (= (str-find (cadr e) "could not open") 0)) (exited) (raise e))))) (def (start) (writectl "start")) (def (startstop) (writectl "startstop") (readnote)) (def (stop) (writectl "stop") (readnote)) (def (waitstop) (unless (eq? (status) 'Stopped) (princ "Waiting... " status "\n") (stop))) (def (follow addr) "Return a list of the next possible executing instructions." :doc-group lsd (lsd-follow addr)) (defmacro (io-pread f off . rest) `(begin (io-pos! ,f ,off) (io-read ,f ,.rest))) (defmacro (io-pwrite f off . rest) `(begin (io-pos! ,f ,off) (io-write ,f ,.rest) (io-flush ,f))) (defmacro (core-read addr . rest) `(begin (unless coref (error "not attached to proc")) (io-pread coref ,addr ,.rest))) (defmacro (core-write addr . rest) `(begin (unless coref (error "not attached to proc")) (io-pwrite coref ,addr ,.rest))) (def (reg-read reg) "Read the value of a register. Examples: (reg-read AX) ; read the return register on amd64 (reg-read PC) ; read the current instruction address." :doc-group lsd :doc-see reg :doc-see registers (unless regsf (error "not attached to proc")) (let {[f (case (reg-type reg) ((:gpreg) regsf) ((:fpreg) fpregsf))]} (io-pread f (reg-addr reg) (reg-size reg)))) (defmacro (symbol-read symbol . rest) "Read the value from the core file at the symbol's address." :doc-group lsd :doc-see local-symbol :doc-see global-symbol `(core-read (symbol-addr ,symbol) ,.rest)) (defmacro (defcval name size) (let {[sizesym `(sym ,size)]} `(def (,name loc (n NIL)) ,(str "Read the " size " value at the given location\n\n" " Optionally read an array of n values\n\n" " The input to this function is as in @.") :doc-see @ :doc-group lsd (if n (core-read (@ loc) ,sizesym n) (core-read (@ loc) ,sizesym))))) (defcval c-byte u8) (defcval c-char utf8) (defcval c-uchar u8) (defcval c-short s16) (defcval c-ushort u16) (defcval c-int s32) (defcval c-uint u32) (defcval c-long s32) (defcval c-ulong u32) (defcval c-vlong s64) (defcval c-uvlong u64) (defcval c-ptr ptr) (defcval c-float f32) (defcval c-double f64) (def (c-str loc (n NIL)) «Read the null-terminated strings at the pointer location. Optionally read a vector of n strings. The input to this function is as in @. Examples: Read all the arguments to the program. (c-str (c-ptr "argv") (c-int "argc")) Read a value `s` of type `char*`. (c-str "s")» :doc-see @ :doc-group lsd (def (readstr loc) (unless coref (error "not attached to proc")) (io-pos! coref loc) (str-trim (io-read-until coref 0) NIL (λ (r) (eq? r #\nul)))) (if n (map 'vec readstr (c-ptr (@ loc) n)) (readstr (c-ptr (@ loc))))) (def (@ loc) «Return the address of local or global data. The input to this function can be: 1. A string that refers to a local or global symbol of the program. 2. A number which is the address itself, 3. A symbol in which case the symbol's address is used. Examples: (@ "argc") ; ↳ #p64(0x7fffffffeed0)» :doc-group lsd (def (symb-addr s) (let {[type (symbol-type s)]} (or (eq? type #\a) (eq? type #\p) (eq? type #\d) (eq? type #\D) (eq? type #\b) (eq? type #\B) (error "expected a data symbol: " s))) (symbol-addr s)) (def (str-addr s) (symb-addr (or (local-symbol s) (global-symbol s :data T) (error "could not find symbol " s)))) (cond ((str? loc) (str-addr loc)) ((num? loc) (ptr loc)) ((symbol? loc) (symb-addr loc)) (else (error "str|num|symbol")))) (def (pc loc) «Return the program instruction address corresponding to a location. A location can be: 1. A string that refers to a symbol (such as a function) in the process's symbol table, 2. A string of the form "file:line" used to find a program address corresponding to given line of source code, 3. A number which is the address itself, 4. A symbol in which case the symbol's address is used. Examples: ; look up a symbol (pc "strecpy") ; ↳ #p64(0x22be91) ; locate source code address (pc "/sys/src/cmd/ls.c:75") ; ↳ #p64(0x20009f)» :doc-see filepc :doc-see symbol :doc-group lsd (def (symb-addr s) (let {[type (symbol-type s)]} (or (eq? type #\t) (eq? type #\T) (error "expected a text symbol: " s))) (symbol-addr s)) (def (str-addr s) (trycatch (filepc s) (λ (e) (when (eq? (car e) 'io-error) (raise e)) (let {[symb (global-symbol s :text T)]} (if symb (symb-addr symb) (error "could not find symbol " s)))))) (cond ((str? loc) (str-addr loc)) ((num? loc) (ptr loc)) ((symbol? loc) (symb-addr loc)) (else (error "str|num|symbol")))) (def (bpsave a) (core-read a 'u8 (size-of bpinst))) (def (bpset at) "Set a breakpoint at the location. Location is as in pc" :doc-see pc :doc-see bpdel :doc-group lsd (waitstop) (let {[addr (pc at)]} (when (has? bptbl addr) (error "breakpoint already set at " at)) (put! bptbl addr (bpsave addr)) (core-write addr bpinst))) (def (bpdel at) "Delete a breakpoint at the location. Location is as in pc" :doc-see pc :doc-see bpset :doc-group lsd (waitstop) (let {[addr (pc at)]} (unless (has? bptbl addr) (error "breakpoint not set at " at)) (core-write addr (get bptbl addr)) (del! bptbl addr))) (def (wpset at size . rest) «Set a watchpoint starting at the location. A breakpoint is emitted for memory access from `at` up to `at+size` bytes. The default access type is to watch for both reads and writes. Location is as in @ Bugs: Only works on 386/amd64. You can only set one watchpoint at a time. Setting a watchpoint clears other watchpoints. Examples: (wpset "anint" 4 :write) ; watch for writes to an integer. (wpset "avlong" 8 :read) ; watch for reads (and writes) to 8 bytes. (wpset "avlong" 8) ; same as previous example.» :doc-see @ :doc-see wpdel :doc-group lsd (waitstop) (or (eq? size 1) (eq? size 2) (eq? size 4) (eq? size 8) (arg-error "invalid size, must be 1, 2, 4 or 8, got: " size)) (def (wptype rest) (let {[type (arr 'rune #\r #\w #\-)] [readset NIL]} (for-each (λ (atype) (case atype (:read (set! readset T type (arr 'rune #\r #\w #\-))) (:write (unless readset (aset! type 0 #\-))) (else (arg-error "invalid watchpoint type " atype)))) rest) type)) (let {[wpf (procfile "watchpt" :write)]} (io-write wpf (str (wptype rest) " " (fixnum (@ at)) " " size "\n")) (io-close wpf))) (def (wpdel) "Deletes all watchpoints." :doc-see wpset :doc-group lsd (waitstop) (let {[wpf (procfile "watchpt" :write)]} (io-write wpf "\n") (io-close wpf))) (def (detach) (when regsf (io-close regsf)) (when fpregsf (io-close fpregsf)) (when coref (io-close coref)) (void)) (def (attach) (detach) (set! regsf (procfile "regs" :read :write) fpregsf (procfile "fpregs" :read :write) coref (procfile "mem" :read :write) pids (cons pid pids)) (void)) (def (status) "Return the running status of the process." :doc-group lsd (let {[sf (procfile "status")]} (prog1 (caddr (read-all sf)) (io-close sf)))) (def (_stk (:level 0) (:n NIL)) "Return the call stack in the form of a list of stack frames. Optionally specify what level of the stack to return, starting at the innermost level and how many levels to display." :doc-group lsd :doc-see frame (let {[s (nthcdr (reverse! (ctrace)) level)]} (if n (subseq s 0 n) s))) (def (stk (:level 0) (:n NIL) (:locals NIL)) "Print the stack trace optionally showing local auto values. Bugs: Prints the values of parameters and autos as unsigned 64 bit integers, not as their actual types." :doc-group lsd :doc-see _stk :doc-see lstk (def (go pc fs) (if (not fs) pc (let* {[f (car fs)] [fsym (frame-sym f)]} (receive (params autos) (partition (λ (x) (equal? #\p (symbol-type x))) (frame-locals f)) (set! params (sort params #.< :key symbol-addr) autos (sort autos #.> :key symbol-addr)) (princ (symbol-name fsym) "(") (princ (str-join (map (λ (p) (str (symbol-name p) "=" (symbol-read p (sym ptr)))) params) ", ")) (princ ")+" (ptr (- pc (symbol-addr fsym))) " ") (princ (src pc) "\n") (when locals (for-each (λ (l) (princ " " (symbol-name l) "=" (symbol-read l (sym ptr)) "\n")) autos)) (go (frame-retpc f) (cdr fs)))))) (let* {[tos (go (curPC) (_stk :level level :n n))] [topsym (lsd-findsym tos)]} (princ (symbol-name topsym) "+" (ptr (- tos (symbol-addr topsym))) " " (src tos) "\n"))) (def (lstk (:level 0) (:n NIL)) "Print the stack trace showing local auto values." :doc-group lsd :doc-see _stk :doc-see stk (stk :locals T :level level :n n)) (def (curPC) (if (>= pid 0) (ptr (reg-read PC)) (error "No active process"))) (def (step (n 1)) "Step `n` assembly instructions. Return the next instruction address to be executed or `NIL` if the program has exited." :doc-group lsd (if (= n 0) (curPC) (let* {[addr (curPC)] [on-bp (has? bptbl addr)]} (when on-bp (core-write addr (get bptbl addr))) (let* {[fs (follow addr)] [os (map bpsave fs)]} (for-each (λ (f) (core-write f bpinst)) fs) (startstop) (for-each (λ (f o) (core-write f o)) fs os) (when on-bp (core-write addr bpinst)) (step (1- n)))))) (def (cont (:print T)) "Continue program execution. Return the next instruction address to be executed or `void` if the program has exited. Optionally print any notes that may have caused the program to stop." :doc-group lsd (when (has? bptbl (curPC)) (step)) (let {[note (startstop)]} (and print (not (void? note)) (princ note "\n"))) (trycatch (curPC) (λ (_) (void)))) (def (func) "Continue program execution until the current function returns." :doc-group lsd (let* {[bp (frame-retpc (car (_stk)))] [o (bpsave bp)]} (core-write bp bpinst) (cont :print NIL) (core-write bp o)) (curPC)) (def (line) "Step one line of the source code. Note: This will step into functions not over." :doc-group lsd (let {[orig (src)]} (def (go) (step) (if (not (equal? orig (src))) (curPC) (go)))