1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-01 12:20:26 +02:00
guile/module/system/vm/debug.scm
Andy Wingo d7a4096d25 implement up, down, frame, and bindings in the repl
* module/system/vm/debug.scm (debugger-repl): Implement up, down, frame,
  and bindings using the new command infrastructure.
2009-12-24 14:20:41 +01:00

322 lines
11 KiB
Scheme
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; Guile VM debugging facilities
;;; Copyright (C) 2001, 2009 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 syntax)
#: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 program)
#:export (run-debugger debug-pre-unwind-handler))
(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)))
;;;
;;; Debugger
;;;
(define-record <debugger> 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)))))
(define* (run-debugger frame #:optional (vm (the-vm)))
(let* ((db (vm-debugger vm))
(level (debugger-level db)))
(dynamic-wind
(lambda () (set! (debugger-level db) (1+ level)))
(lambda () (debugger-repl db frame))
(lambda () (set! (debugger-level db) level)))))
(define (debugger-repl db frame)
(let ((top frame)
(cur frame)
(index 0)
(level (debugger-level db)))
(define (frame-index frame)
(let lp ((idx 0) (walk top))
(if (= (frame-return-address frame) (frame-return-address walk))
idx
(lp (1+ idx) (frame-previous walk)))))
(define (frame-at-index idx)
(let lp ((idx idx) (walk top))
(cond
((not walk) #f)
((zero? idx) walk)
(else (lp (1+ idx) (frame-previous walk))))))
(define (show-frame)
; #2 0x009600e0 in do_std_select (args=0xbfffd9e0) at threads.c:1668
; 1668 select (select_args->nfds,
(let ((p (frame-procedure cur)))
(format #t "#~2a 0x~8,'0x in ~s~%" index (frame-instruction-pointer cur)
(cons (or (procedure-name p) p) (frame-arguments 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)
(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)
"Print a backtrace of all stack frames, or innermost COUNT frames."
(display-backtrace (make-stack top) (current-output-port) #f count))
(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"
(if (or (not (integer? count)) (<= count 0))
(format #t "Invalid argument to `up': expected a positive integer for COUNT.~%")
(let lp ((n count))
(cond
((zero? n) (show-frame))
((frame-previous cur)
=> (lambda (new)
(set! cur new)
(set! index (1+ index))
(lp (1- n))))
((= n count)
(format #t "Already at outermost frame.\n"))
(else
(format #t "Reached outermost frame after walking ~a frames.\n"
(- count n))
(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 0)
(format #t "Already at innermost frame.~%"))
(else
(set! index (max (- index count) 0))
(set! cur (frame-at-index 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 bindings))
"Show some information about locally-bound variables in the selected frame."
(format #t "~a\n" (frame-bindings 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)
(string<? (symbol->string 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))
(nth cmd))
((eof-object? cmd)
(newline)
(throw 'quit))
(else
(format (current-error-port)
"~&Unknown command: ~a. Try `help'.~%" cmd)
*unspecified*)))
(catch 'quit
(lambda ()
(let loop ()
(apply
handle
(save-module-excursion
(lambda ()
(set-current-module commands)
(read-args prompt))))
(loop)))
(lambda (k . args)
(apply values args))))))
;; things this debugger should do:
;;
;; eval expression in context of frame
;; up/down stack for inspecting
;; print procedure and args for frame
;; print local variables for frame
;; set local variable in frame
;; display backtrace
;; display full backtrace
;; 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)
;; help
;; disassemble a function
;; disassemble the current function
;; inspect any object
;; hm, trace via reassigning global vars. tricksy.
;; (state associated with vm ?)
(define (debug-pre-unwind-handler key . args)
;; herald
(format #t "Throw to key `~a' with args `~s'.
Entering the debugger. Type `bt' for a backtrace or `c' to continue.
This debugger implementation is temporary. See system/vm/debug.scm for
some ideas on how to make it better.\n" key args)
(run-debugger (stack-ref (make-stack #t) 1))
(save-stack 1)
(apply throw key args))