diff --git a/module/Makefile.am b/module/Makefile.am index f445ac1b6..a2fb0f311 100644 --- a/module/Makefile.am +++ b/module/Makefile.am @@ -310,7 +310,6 @@ OOP_SOURCES = \ SYSTEM_SOURCES = \ system/vm/inspect.scm \ system/vm/coverage.scm \ - system/vm/debug.scm \ system/vm/frame.scm \ system/vm/instruction.scm \ system/vm/objcode.scm \ diff --git a/module/system/repl/debug.scm b/module/system/repl/debug.scm index 1164c6bb5..01b1b4cec 100644 --- a/module/system/repl/debug.scm +++ b/module/system/repl/debug.scm @@ -34,7 +34,29 @@ print-locals print-frame print-frames frame->module stack->vector narrow-stack->vector)) -;;; FIXME: add more repl meta-commands: continue, inspect, etc... +;; TODO: +;; +;; Update this TODO list ;) +;; partial meta-commands (,qui -> ,quit) +;; eval expression in context of frame +;; set local variable in frame +;; step until next instruction +;; step until next function call/return +;; step until return from frame +;; step until different source line +;; step until greater source line +;; watch expression +;; break on a function +;; remove breakpoints +;; set printing width +;; display a truncated backtrace +;; go to a frame by index +;; (reuse gdb commands perhaps) +;; disassemble a function +;; disassemble the current function +;; inspect any object +;; hm, trace via reassigning global vars. tricksy. +;; (state associated with vm ?) ;;; ;;; Debugger @@ -175,3 +197,13 @@ (define (narrow-stack->vector stack . args) (stack->vector (apply make-stack (stack-ref stack 0) args))) +;; (define (debug) +;; (run-debugger +;; (narrow-stack->vector +;; (make-stack #t) +;; ;; Narrow the `make-stack' frame and the `debug' frame +;; 2 +;; ;; Narrow the end of the stack to the most recent start-stack. +;; (and (pair? (fluid-ref %stacks)) +;; (cdar (fluid-ref %stacks)))))) + diff --git a/module/system/vm/debug.scm b/module/system/vm/debug.scm deleted file mode 100644 index dfc7140d8..000000000 --- a/module/system/vm/debug.scm +++ /dev/null @@ -1,522 +0,0 @@ -;;; Guile VM debugging facilities - -;;; Copyright (C) 2001, 2009, 2010 Free Software Foundation, Inc. -;;; -;;; This library is free software; you can redistribute it and/or -;;; modify it under the terms of the GNU Lesser General Public -;;; License as published by the Free Software Foundation; either -;;; version 3 of the License, or (at your option) any later version. -;;; -;;; This library is distributed in the hope that it will be useful, -;;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -;;; Lesser General Public License for more details. -;;; -;;; You should have received a copy of the GNU Lesser General Public -;;; License along with this library; if not, write to the Free Software -;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -;;; Code: - -(define-module (system vm debug) - #:use-module (system base pmatch) - #:use-module (system base syntax) - #:use-module (system base language) - #:use-module (system vm vm) - #:use-module (system vm frame) - #:use-module (ice-9 rdelim) - #:use-module (ice-9 pretty-print) - #:use-module (ice-9 format) - #:use-module ((system vm inspect) #:select ((inspect . %inspect))) - #:use-module (system vm program) - #:export (*debug-input-port* - *debug-output-port* - debug run-debugger - call-with-error-handling with-error-handling)) - - - -(define *debug-input-port* (make-fluid)) -(define *debug-output-port* (make-fluid)) - -(define (debug-input-port) - (or (fluid-ref *debug-input-port*) - (current-input-port))) -(define (debug-output-port) - (or (fluid-ref *debug-output-port*) - (current-error-port))) - - -(define (reverse-hashq h) - (let ((ret (make-hash-table))) - (hash-for-each - (lambda (k v) - (hashq-set! ret v (cons k (hashq-ref ret v '())))) - h) - ret)) - -(define (catch-bad-arguments thunk bad-args-thunk) - (catch 'wrong-number-of-args - (lambda () - (catch 'keyword-argument-error - thunk - (lambda (k . args) - (bad-args-thunk)))) - (lambda (k . args) - (bad-args-thunk)))) - -(define (read-args prompt) - (define (read* reader) - (repl-reader prompt reader)) - (define (next) - (read* read-char)) - (define (cmd chr) - (cond - ((eof-object? chr) (list chr)) - ((char=? chr #\newline) (cmd (next))) - ((char-whitespace? chr) (cmd (next))) - (else - (unread-char chr) - (let ((tok (read* read))) - (args (list tok) (next)))))) - (define (args out chr) - (cond - ((eof-object? chr) (reverse out)) - ((char=? chr #\newline) (reverse out)) - ((char-whitespace? chr) (args out (next))) - (else - (unread-char chr) - (let ((tok (read* read))) - (args (cons tok out) (next)))))) - (cmd (next))) - -(define* (print-locals frame #:optional (port (current-output-port)) - #:key (width 72) (per-line-prefix "")) - (let ((bindings (frame-bindings frame))) - (cond - ((null? bindings) - (format port "~aNo local variables.~%" per-line-prefix)) - (else - (format port "~aLocal variables:~%" per-line-prefix) - (for-each - (lambda (binding) - (format port "~a~4d ~a~:[~; (boxed)~] = ~v:@y\n" - per-line-prefix - (binding:index binding) - (binding:name binding) - (binding:boxed? binding) - width - (let ((x (frame-local-ref frame (binding:index binding)))) - (if (binding:boxed? binding) - (variable-ref x) - x)))) - (frame-bindings frame)))))) - -(define* (print-frames frames - #:optional (port (current-output-port)) - #:key (width 72) (full? #f) (forward? #f) count) - (let* ((len (vector-length frames)) - (lower-idx (if (or (not count) (positive? count)) - 0 - (max 0 (+ len count)))) - (upper-idx (if (and count (negative? count)) - (1- len) - (1- (if count (min count len) len)))) - (inc (if forward? 1 -1))) - (let lp ((i (if forward? lower-idx upper-idx)) - (last-file "")) - (if (<= lower-idx i upper-idx) - (let* ((frame (vector-ref frames i)) - (source (frame-source frame)) - (file (and source - (or (source:file source) - "current input"))) - (line (and=> source source:line))) - (if (and file (not (equal? file last-file))) - (format port "~&In ~a:~&" file)) - (format port "~:[~*~6_~;~5d:~]~3d ~v:@y~%" line line - i width (frame-call-representation frame)) - (if full? - (print-locals frame #:width width - #:per-line-prefix " ")) - (lp (+ i inc) (or file last-file))))))) - -;; Ideally here we would have something much more syntactic, in that a set! to a -;; local var that is not settable would raise an error, and export etc forms -;; would modify the module in question: but alack, this is what we have now. -;; Patches welcome! -(define (frame->module frame) - (let ((proc (frame-procedure frame))) - (if (program? proc) - (let* ((mod (or (program-module proc) (current-module))) - (mod* (make-module))) - (module-use! mod* mod) - (for-each - (lambda (binding) - (let* ((x (frame-local-ref frame (binding:index binding))) - (var (if (binding:boxed? binding) x (make-variable x)))) - (format (debug-output-port) - "~:[Read-only~;Mutable~] local variable ~a = ~70:@y\n" - (binding:boxed? binding) - (binding:name binding) - (if (variable-bound? var) (variable-ref var) var)) - (module-add! mod* (binding:name binding) var))) - (frame-bindings frame)) - mod*) - (current-module)))) - - -;;; -;;; Debugger -;;; - -(define-record vm level breakpoints module) - -(define (make-debugger-module) - (let ((m (make-fresh-user-module))) - m)) - -(define vm-debugger - (let ((prop (make-object-property))) - (lambda (vm) - (or (prop vm) - (let ((debugger (make-debugger vm 0 '() (make-debugger-module)))) - (set! (prop vm) debugger) - debugger))))) - -;; FIXME: Instead of dynamically binding the input and output ports in the -;; context of the error, the debugger should really be a kind of coroutine, -;; having its own dynamic input and output bindings. Delimited continuations can -;; do this. -(define* (run-debugger frames #:optional (vm (the-vm)) #:key - (input (debug-input-port)) (output (debug-output-port))) - (let* ((db (vm-debugger vm)) - (level (debugger-level db))) - (dynamic-wind - (lambda () - (set! (debugger-level db) (1+ level)) - (set! input (set-current-input-port input))) - (lambda () - (dynamic-wind - (lambda () (set! output (set-current-output-port output))) - (lambda () (debugger-repl db frames)) - (lambda () (set! output (set-current-output-port output))))) - (lambda () - (set! input (set-current-input-port input)) - (set! (debugger-level db) level))))) - -(define (debugger-repl db frames) - (let* ((index 0) - (top (vector-ref frames index)) - (cur top) - (level (debugger-level db)) - (last #f)) - (define (frame-at-index idx) - (and (< idx (vector-length frames)) - (vector-ref frames idx))) - (define (show-frame) - ;; #2 0x009600e0 in do_std_select (args=0xbfffd9e0) at threads.c:1668 - ;; 1668 select (select_args->nfds, - (format #t "#~2a 0x~8,'0x in ~60@y~%" - index - (frame-instruction-pointer cur) - (frame-call-representation cur))) - - (define-syntax define-command - (syntax-rules () - ((_ ((mod cname alias ...) . args) body ...) - (define cname - (let ((c (lambda* args body ...))) - (set-procedure-property! c 'name 'cname) - (module-define! mod 'cname c) - (module-add! mod 'alias (module-local-variable mod 'cname)) - ... - c))))) - - (let ((commands (make-module))) - (define (prompt) - (format #f "~a~a debug> " - (if (= level 1) - "" - (format #f "~a:" level)) - index)) - - (define (print* . vals) - (define (print x) - (run-hook before-print-hook x) - (set! last x) - (pretty-print x)) - (if (and (pair? vals) - (not (and (null? (cdr vals)) - (unspecified? (car vals))))) - (for-each print vals))) - - (define-command ((commands backtrace bt) #:optional count - #:key (width 72) full?) - "Print a backtrace of all stack frames, or innermost COUNT frames. -If COUNT is negative, the last COUNT frames will be shown." - (print-frames frames - #:count count - #:width width - #:full? full?)) - - (define-command ((commands up) #:optional (count 1)) - "Select and print stack frames that called this one. -An argument says how many frames up to go" - (cond - ((or (not (integer? count)) (<= count 0)) - (format #t "Invalid argument to `up': expected a positive integer for COUNT.~%")) - ((>= (+ count index) (vector-length frames)) - (cond - ((= index (1- (vector-length frames))) - (format #t "Already at outermost frame.\n")) - (else - (set! index (1- (vector-length frames))) - (set! cur (vector-ref frames index)) - (show-frame)))) - (else - (set! index (+ count index)) - (set! cur (vector-ref frames index)) - (show-frame)))) - - (define-command ((commands down) #:optional (count 1)) - "Select and print stack frames called by this one. -An argument says how many frames down to go" - (cond - ((or (not (integer? count)) (<= count 0)) - (format #t "Invalid argument to `down': expected a positive integer for COUNT.~%")) - ((< (- index count) 0) - (cond - ((zero? index) - (format #t "Already at innermost frame.\n")) - (else - (set! index 0) - (set! cur (vector-ref frames index)) - (show-frame)))) - (else - (set! index (- index count)) - (set! cur (vector-ref frames index)) - (show-frame)))) - - (define-command ((commands frame f) #:optional idx) - "Show the selected frame. -With an argument, select a frame by index, then show it." - (cond - (idx - (cond - ((or (not (integer? idx)) (< idx 0)) - (format #t "Invalid argument to `frame': expected a non-negative integer for IDX.~%")) - ((frame-at-index idx) - => (lambda (f) - (set! cur f) - (set! index idx) - (show-frame))) - (else - (format #t "No such frame.~%")))) - (else (show-frame)))) - - (define-command ((commands repl r)) - "Run a new REPL in the context of the current frame." - (save-module-excursion - (lambda () - (set-current-module (frame->module cur)) - ((@ (system repl repl) start-repl))))) - - (define-command ((commands procedure proc)) - "Print the procedure for the selected frame." - (print* (frame-procedure cur))) - - (define-command ((commands inspect i)) - "Launch the inspector on the last-printed object." - (%inspect last)) - - (define-command ((commands locals)) - "Show locally-bound variables in the selected frame." - (print-locals cur)) - - (define-command ((commands quit q continue cont c)) - "Quit the debugger and let the program continue executing." - (throw 'quit)) - - (define-command ((commands help h ?) #:optional cmd) - "Show this help message." - (let ((rhash (reverse-hashq (module-obarray commands)))) - (define (help-cmd cmd) - (let* ((v (module-local-variable commands cmd)) - (p (variable-ref v)) - (canonical-name (procedure-name p))) - ;; la la la - (format #t "~a~{ ~:@(~a~)~}~?~%~a~&~%" - canonical-name (program-lambda-list p) - "~#[~:;~40t(aliases: ~@{~a~^, ~})~]" - (delq canonical-name (hashq-ref rhash v)) - (procedure-documentation p)))) - (cond - (cmd - (cond - ((and (symbol? cmd) (module-local-variable commands cmd)) - (help-cmd cmd)) - (else - (format #t "Invalid command ~s.~%" cmd) - (format #t "Try `help' for a list of commands~%")))) - (else - (let ((names (sort - (hash-map->list - (lambda (k v) - (procedure-name (variable-ref k))) - rhash) - (lambda (x y) - (stringstring x) - (symbol->string y)))))) - (format #t "Available commands:~%~%") - (for-each help-cmd names)))))) - - (define (handle cmd . args) - (cond - ((and (symbol? cmd) - (module-local-variable commands cmd)) - => (lambda (var) - (let ((proc (variable-ref var))) - (catch-bad-arguments - (lambda () - (apply (variable-ref var) args)) - (lambda () - (format (current-error-port) - "Invalid arguments to ~a. Try `help ~a'.~%" - (procedure-name proc) (procedure-name proc))))))) - ((and (integer? cmd) (exact? cmd)) - (frame cmd)) - ((eof-object? cmd) - (newline) - (throw 'quit)) - (else - (format (current-error-port) - "~&Unknown command: ~a. Try `help'.~%" cmd) - *unspecified*))) - - (catch 'quit - (lambda () - (let loop () - (let ((args (call-with-error-handling - (lambda () - (save-module-excursion - (lambda () - (set-current-module commands) - (read-args prompt)))) - #:on-error 'pass))) - ;; args will be unspecified if there was a read error. - (if (not (unspecified? args)) - (apply handle args)) - (loop)))) - (lambda (k . args) - (apply values args)))))) - - -;; TODO: -;; -;; eval expression in context of frame -;; set local variable in frame -;; step until next instruction -;; step until next function call/return -;; step until return from frame -;; step until different source line -;; step until greater source line -;; watch expression -;; break on a function -;; remove breakpoints -;; set printing width -;; display a truncated backtrace -;; go to a frame by index -;; (reuse gdb commands perhaps) -;; disassemble a function -;; disassemble the current function -;; inspect any object -;; hm, trace via reassigning global vars. tricksy. -;; (state associated with vm ?) - -(define (stack->vector stack) - (let* ((len (stack-length stack)) - (v (make-vector len))) - (if (positive? len) - (let lp ((i 0) (frame (stack-ref stack 0))) - (if (< i len) - (begin - (vector-set! v i frame) - (lp (1+ i) (frame-previous frame)))))) - v)) - -(define (debug) - (run-debugger - (narrow-stack->vector - (make-stack #t) - ;; Narrow the `make-stack' frame and the `debug' frame - 2 - ;; Narrow the end of the stack to the most recent start-stack. - (and (pair? (fluid-ref %stacks)) - (cdar (fluid-ref %stacks)))))) - -(define (narrow-stack->vector stack . args) - (stack->vector (apply make-stack (stack-ref stack 0) args))) - -(define* (call-with-error-handling thunk #:key - (on-error 'debug) (post-error 'catch) - (pass-keys '(quit))) - (catch #t - (lambda () (%start-stack #t thunk)) - - (case post-error - ((catch) - (lambda (key . args) - (if (memq key pass-keys) - (apply throw key args) - (let ((cep (current-error-port))) - (pmatch args - ((,subr ,msg ,args . ,rest) - (run-hook before-error-hook) - (display-error #f cep subr msg args rest) - (run-hook after-error-hook) - (force-output cep)) - (else - (format cep "\nERROR: uncaught throw to `~a', args: ~a\n" - key args))) - (if #f #f))))) - (else - (if (procedure? post-error) - post-error - (error "Unknown post-error strategy" post-error)))) - - (case on-error - ((debug) - (lambda (key . args) - (let ((stack (make-stack #t)) - (dep (debug-output-port))) - (pmatch args - ((,subr ,msg ,args . ,rest) - (format dep "Throw to key `~a':\n" key) - (display-error stack dep subr msg args rest)) - (else - (format dep "Throw to key `~a' with args `~s'." key args))) - (format dep "Entering the debugger. Type `bt' for a backtrace") - (format dep " or `c' to continue.\n") - (run-debugger - (narrow-stack->vector - stack - ;; Cut three frames from the top of the stack: make-stack, this - ;; one, and the throw handler. - 3 - ;; Narrow the end of the stack to the most recent start-stack. - (and (pair? (fluid-ref %stacks)) - (cdar (fluid-ref %stacks)))))))) - ((pass) - (lambda (key . args) - ;; fall through to rethrow - #t)) - (else - (if (procedure? on-error) - on-error - (error "Unknown on-error strategy" on-error)))))) - -(define-syntax with-error-handling - (syntax-rules () - ((_ form) - (call-with-error-handling (lambda () form)))))