mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-02 04:40:29 +02:00
592 lines
21 KiB
Scheme
Executable file
592 lines
21 KiB
Scheme
Executable file
(define-module (ice-9 gds-client)
|
|
#:use-module (oop goops)
|
|
#:use-module (oop goops describe)
|
|
#:use-module (ice-9 debugging trace)
|
|
#:use-module (ice-9 debugging traps)
|
|
#:use-module (ice-9 debugging trc)
|
|
#:use-module (ice-9 debugging steps)
|
|
#:use-module (ice-9 pretty-print)
|
|
#:use-module (ice-9 regex)
|
|
#:use-module (ice-9 session)
|
|
#:use-module (ice-9 string-fun)
|
|
#:export (gds-debug-trap
|
|
run-utility
|
|
gds-accept-input))
|
|
|
|
(cond ((string>=? (version) "1.7")
|
|
(use-modules (ice-9 debugger utils)))
|
|
(else
|
|
(define the-ice-9-debugger-module (resolve-module '(ice-9 debugger)))
|
|
(module-export! the-ice-9-debugger-module
|
|
'(source-position
|
|
write-frame-short/application
|
|
write-frame-short/expression
|
|
write-frame-args-long
|
|
write-frame-long))))
|
|
|
|
(use-modules (ice-9 debugger))
|
|
|
|
(define gds-port #f)
|
|
|
|
;; Return an integer that somehow identifies the current thread.
|
|
(define (get-thread-id)
|
|
(let ((root (dynamic-root)))
|
|
(cond ((integer? root)
|
|
root)
|
|
((pair? root)
|
|
(object-address root))
|
|
(else
|
|
(error "Unexpected dynamic root:" root)))))
|
|
|
|
;; gds-debug-read is a high-priority read. The (debug-thread-id ID)
|
|
;; form causes the frontend to dismiss any reads from threads whose id
|
|
;; is not ID, until it receives the (thread-id ...) form with the same
|
|
;; id as ID. Dismissing the reads of any other threads (by sending a
|
|
;; form that is otherwise ignored) causes those threads to release the
|
|
;; read mutex, which allows the (gds-read) here to proceed.
|
|
(define (gds-debug-read)
|
|
(write-form `(debug-thread-id ,(get-thread-id)))
|
|
(gds-read))
|
|
|
|
(define (gds-debug-trap trap-context)
|
|
"Invoke the GDS debugger to explore the stack at the specified trap."
|
|
(connect-to-gds)
|
|
(start-stack 'debugger
|
|
(let* ((stack (tc:stack trap-context))
|
|
(flags1 (let ((trap-type (tc:type trap-context)))
|
|
(case trap-type
|
|
((#:return #:error)
|
|
(list trap-type
|
|
(tc:return-value trap-context)))
|
|
(else
|
|
(list trap-type)))))
|
|
(flags (if (tc:continuation trap-context)
|
|
(cons #:continuable flags1)
|
|
flags1))
|
|
(fired-traps (tc:fired-traps trap-context))
|
|
(special-index (and (= (length fired-traps) 1)
|
|
(is-a? (car fired-traps) <exit-trap>)
|
|
(eq? (tc:type trap-context) #:return)
|
|
(- (tc:depth trap-context)
|
|
(slot-ref (car fired-traps) 'depth)))))
|
|
;; Write current stack to the frontend.
|
|
(write-form (list 'stack
|
|
(if (and special-index (> special-index 0))
|
|
special-index
|
|
0)
|
|
(stack->emacs-readable stack)
|
|
(append (flags->emacs-readable flags)
|
|
(slot-ref trap-context
|
|
'handler-return-syms))))
|
|
;; Now wait for instruction.
|
|
(let loop ((protocol (gds-debug-read)))
|
|
;; Act on it.
|
|
(case (car protocol)
|
|
((tweak)
|
|
;; Request to tweak the handler return value.
|
|
(let ((tweaking (catch #t
|
|
(lambda ()
|
|
(list (with-input-from-string
|
|
(cadr protocol)
|
|
read)))
|
|
(lambda ignored #f))))
|
|
(if tweaking
|
|
(slot-set! trap-context
|
|
'handler-return-value
|
|
(cons 'instead (car tweaking)))))
|
|
(loop (gds-debug-read)))
|
|
((continue)
|
|
;; Continue (by exiting the debugger).
|
|
*unspecified*)
|
|
((evaluate)
|
|
;; Evaluate expression in specified frame.
|
|
(eval-in-frame stack (cadr protocol) (caddr protocol))
|
|
(loop (gds-debug-read)))
|
|
((info-frame)
|
|
;; Return frame info.
|
|
(let ((frame (stack-ref stack (cadr protocol))))
|
|
(write-form (list 'info-result
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(write-frame-long frame))))))
|
|
(loop (gds-debug-read)))
|
|
((info-args)
|
|
;; Return frame args.
|
|
(let ((frame (stack-ref stack (cadr protocol))))
|
|
(write-form (list 'info-result
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(write-frame-args-long frame))))))
|
|
(loop (gds-debug-read)))
|
|
((proc-source)
|
|
;; Show source of application procedure.
|
|
(let* ((frame (stack-ref stack (cadr protocol)))
|
|
(proc (frame-procedure frame))
|
|
(source (and proc (procedure-source proc))))
|
|
(write-form (list 'info-result
|
|
(if source
|
|
(sans-surrounding-whitespace
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(pretty-print source))))
|
|
(if proc
|
|
"This procedure is coded in C"
|
|
"This frame has no procedure")))))
|
|
(loop (gds-debug-read)))
|
|
((traps-here)
|
|
;; Show the traps that fired here.
|
|
(write-form (list 'info-result
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(for-each describe
|
|
(tc:fired-traps trap-context))))))
|
|
(loop (gds-debug-read)))
|
|
((step-into)
|
|
;; Set temporary breakpoint on next trap.
|
|
(at-step gds-debug-trap
|
|
1
|
|
#f
|
|
(if (memq #:return flags)
|
|
#f
|
|
(- (stack-length stack)
|
|
(cadr protocol)))))
|
|
((step-over)
|
|
;; Set temporary breakpoint on exit from
|
|
;; specified frame.
|
|
(at-exit (- (stack-length stack) (cadr protocol))
|
|
gds-debug-trap))
|
|
((step-file)
|
|
;; Set temporary breakpoint on next trap in same
|
|
;; source file.
|
|
(at-step gds-debug-trap
|
|
1
|
|
(frame-file-name (stack-ref stack
|
|
(cadr protocol)))
|
|
(if (memq #:return flags)
|
|
#f
|
|
(- (stack-length stack)
|
|
(cadr protocol)))))
|
|
(else
|
|
(safely-handle-nondebug-protocol protocol)
|
|
(loop (gds-debug-read))))))))
|
|
|
|
(define (connect-to-gds . application-name)
|
|
(or gds-port
|
|
(begin
|
|
(set! gds-port
|
|
(or (let ((s (socket PF_INET SOCK_STREAM 0))
|
|
(SOL_TCP 6)
|
|
(TCP_NODELAY 1))
|
|
(setsockopt s SOL_TCP TCP_NODELAY 1)
|
|
(catch #t
|
|
(lambda ()
|
|
(connect s AF_INET (inet-aton "127.0.0.1") 8333)
|
|
s)
|
|
(lambda _ #f)))
|
|
(let ((s (socket PF_UNIX SOCK_STREAM 0)))
|
|
(catch #t
|
|
(lambda ()
|
|
(connect s AF_UNIX "/tmp/.gds_socket")
|
|
s)
|
|
(lambda _ #f)))
|
|
(error "Couldn't connect to GDS by TCP or Unix domain socket")))
|
|
(write-form (list 'name (getpid) (apply client-name application-name))))))
|
|
|
|
(define (client-name . application-name)
|
|
(let loop ((args (append application-name (program-arguments))))
|
|
(if (null? args)
|
|
(format #f "PID ~A" (getpid))
|
|
(let ((arg (car args)))
|
|
(cond ((string-match "^(.*[/\\])?guile(\\..*)?$" arg)
|
|
(loop (cdr args)))
|
|
((string-match "^-" arg)
|
|
(loop (cdr args)))
|
|
(else
|
|
(format #f "~A (PID ~A)" arg (getpid))))))))
|
|
|
|
(if (not (defined? 'make-mutex))
|
|
(begin
|
|
(define (make-mutex) #f)
|
|
(define lock-mutex noop)
|
|
(define unlock-mutex noop)))
|
|
|
|
(define write-mutex (make-mutex))
|
|
|
|
(define (write-form form)
|
|
;; Write any form FORM to GDS.
|
|
(lock-mutex write-mutex)
|
|
(write form gds-port)
|
|
(newline gds-port)
|
|
(force-output gds-port)
|
|
(unlock-mutex write-mutex))
|
|
|
|
(define (stack->emacs-readable stack)
|
|
;; Return Emacs-readable representation of STACK.
|
|
(map (lambda (index)
|
|
(frame->emacs-readable (stack-ref stack index)))
|
|
(iota (min (stack-length stack)
|
|
(cadr (memq 'depth (debug-options)))))))
|
|
|
|
(define (frame->emacs-readable frame)
|
|
;; Return Emacs-readable representation of FRAME.
|
|
(if (frame-procedure? frame)
|
|
(list 'application
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(display (if (frame-real? frame) " " "t "))
|
|
(write-frame-short/application frame)))
|
|
(source->emacs-readable frame))
|
|
(list 'evaluation
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(display (if (frame-real? frame) " " "t "))
|
|
(write-frame-short/expression frame)))
|
|
(source->emacs-readable frame))))
|
|
|
|
(define (source->emacs-readable frame)
|
|
;; Return Emacs-readable representation of the filename, line and
|
|
;; column source properties of SOURCE.
|
|
(or (frame->source-position frame) 'nil))
|
|
|
|
(define (flags->emacs-readable flags)
|
|
;; Return Emacs-readable representation of trap FLAGS.
|
|
(let ((prev #f))
|
|
(map (lambda (flag)
|
|
(let ((erf (if (and (keyword? flag)
|
|
(not (eq? prev #:return)))
|
|
(keyword->symbol flag)
|
|
(format #f "~S" flag))))
|
|
(set! prev flag)
|
|
erf))
|
|
flags)))
|
|
|
|
(define (eval-in-frame stack index expr)
|
|
(write-form
|
|
(list 'eval-result
|
|
(format #f "~S"
|
|
(catch #t
|
|
(lambda ()
|
|
(local-eval (with-input-from-string expr read)
|
|
(memoized-environment
|
|
(frame-source (stack-ref stack
|
|
index)))))
|
|
(lambda args
|
|
(cons 'ERROR args)))))))
|
|
|
|
(set! (behaviour-ordering gds-debug-trap) 100)
|
|
|
|
;;; Code below here adds support for interaction between the GDS
|
|
;;; client program and the Emacs frontend even when not stopped in the
|
|
;;; debugger.
|
|
|
|
;; A mutex to control attempts by multiple threads to read protocol
|
|
;; back from the frontend.
|
|
(define gds-read-mutex (make-mutex))
|
|
|
|
;; Read a protocol instruction from the frontend.
|
|
(define (gds-read)
|
|
;; Acquire the read mutex.
|
|
(lock-mutex gds-read-mutex)
|
|
;; Tell the front end something that identifies us as a thread.
|
|
(write-form `(thread-id ,(get-thread-id)))
|
|
;; Now read, then release the mutex and return what was read.
|
|
(let ((x (catch #t
|
|
(lambda () (read gds-port))
|
|
(lambda ignored the-eof-object))))
|
|
(unlock-mutex gds-read-mutex)
|
|
x))
|
|
|
|
(define (gds-accept-input exit-on-continue)
|
|
;; If reading from the GDS connection returns EOF, we will throw to
|
|
;; this catch.
|
|
(catch 'server-eof
|
|
(lambda ()
|
|
(let loop ((protocol (gds-read)))
|
|
(if (or (eof-object? protocol)
|
|
(and exit-on-continue
|
|
(eq? (car protocol) 'continue)))
|
|
(throw 'server-eof))
|
|
(safely-handle-nondebug-protocol protocol)
|
|
(loop (gds-read))))
|
|
(lambda ignored #f)))
|
|
|
|
(define (safely-handle-nondebug-protocol protocol)
|
|
;; This catch covers any internal errors in the GDS code or
|
|
;; protocol.
|
|
(catch #t
|
|
(lambda ()
|
|
(lazy-catch #t
|
|
(lambda ()
|
|
(handle-nondebug-protocol protocol))
|
|
save-lazy-trap-context-and-rethrow))
|
|
(lambda (key . args)
|
|
(write-form
|
|
`(eval-results (error . ,(format #f "~s" protocol))
|
|
,(if last-lazy-trap-context 't 'nil)
|
|
"GDS Internal Error
|
|
Please report this to <neil@ossau.uklinux.net>, ideally including:
|
|
- a description of the scenario in which this error occurred
|
|
- which versions of Guile and guile-debugging you are using
|
|
- the error stack, which you can get by clicking on the link below,
|
|
and then cut and paste into your report.
|
|
Thanks!\n\n"
|
|
,(list (with-output-to-string
|
|
(lambda ()
|
|
(write key)
|
|
(display ": ")
|
|
(write args)
|
|
(newline)))))))))
|
|
|
|
;; The key that is used to signal a read error changes from 1.6 to
|
|
;; 1.8; here we cover all eventualities by discovering the key
|
|
;; dynamically.
|
|
(define read-error-key
|
|
(catch #t
|
|
(lambda ()
|
|
(with-input-from-string "(+ 3 4" read))
|
|
(lambda (key . args)
|
|
key)))
|
|
|
|
(define (handle-nondebug-protocol protocol)
|
|
(case (car protocol)
|
|
|
|
((eval)
|
|
(set! last-lazy-trap-context #f)
|
|
(apply (lambda (correlator module port-name line column code flags)
|
|
(with-input-from-string code
|
|
(lambda ()
|
|
(set-port-filename! (current-input-port) port-name)
|
|
(set-port-line! (current-input-port) line)
|
|
(set-port-column! (current-input-port) column)
|
|
(let ((m (and module (resolve-module-from-root module))))
|
|
(catch read-error-key
|
|
(lambda ()
|
|
(let loop ((exprs '()) (x (read)))
|
|
(if (eof-object? x)
|
|
;; Expressions to be evaluated have all
|
|
;; been read. Now evaluate them.
|
|
(let loop2 ((exprs (reverse! exprs))
|
|
(results '())
|
|
(n 1))
|
|
(if (null? exprs)
|
|
(write-form `(eval-results ,correlator
|
|
,(if last-lazy-trap-context 't 'nil)
|
|
,@results))
|
|
(loop2 (cdr exprs)
|
|
(append results (gds-eval (car exprs) m
|
|
(if (and (null? (cdr exprs))
|
|
(= n 1))
|
|
#f n)))
|
|
(+ n 1))))
|
|
;; Another complete expression read; add
|
|
;; it to the list.
|
|
(begin
|
|
(if (and (pair? x)
|
|
(memq 'debug flags))
|
|
(install-trap (make <source-trap>
|
|
#:expression x
|
|
#:behaviour gds-debug-trap)))
|
|
(loop (cons x exprs) (read))))))
|
|
(lambda (key . args)
|
|
(write-form `(eval-results
|
|
,correlator
|
|
,(if last-lazy-trap-context 't 'nil)
|
|
,(with-output-to-string
|
|
(lambda ()
|
|
(display ";;; Reading expressions")
|
|
(display " to evaluate\n")
|
|
(apply display-error #f
|
|
(current-output-port) args)))
|
|
("error-in-read")))))))))
|
|
(cdr protocol)))
|
|
|
|
((complete)
|
|
(let ((matches (apropos-internal
|
|
(string-append "^" (regexp-quote (cadr protocol))))))
|
|
(cond ((null? matches)
|
|
(write-form '(completion-result nil)))
|
|
(else
|
|
;;(write matches (current-error-port))
|
|
;;(newline (current-error-port))
|
|
(let ((match
|
|
(let loop ((match (symbol->string (car matches)))
|
|
(matches (cdr matches)))
|
|
;;(write match (current-error-port))
|
|
;;(newline (current-error-port))
|
|
;;(write matches (current-error-port))
|
|
;;(newline (current-error-port))
|
|
(if (null? matches)
|
|
match
|
|
(if (string-prefix=? match
|
|
(symbol->string (car matches)))
|
|
(loop match (cdr matches))
|
|
(loop (substring match 0
|
|
(- (string-length match) 1))
|
|
matches))))))
|
|
(if (string=? match (cadr protocol))
|
|
(write-form `(completion-result
|
|
,(map symbol->string matches)))
|
|
(write-form `(completion-result
|
|
,match))))))))
|
|
|
|
((debug-lazy-trap-context)
|
|
(if last-lazy-trap-context
|
|
(gds-debug-trap last-lazy-trap-context)
|
|
(error "There is no stack available to show")))
|
|
|
|
(else
|
|
(error "Unexpected protocol:" protocol))))
|
|
|
|
(define (resolve-module-from-root name)
|
|
(save-module-excursion
|
|
(lambda ()
|
|
(set-current-module the-root-module)
|
|
(resolve-module name))))
|
|
|
|
(define (gds-eval x m part)
|
|
;; Consumer to accept possibly multiple values and present them for
|
|
;; Emacs as a list of strings.
|
|
(define (value-consumer . values)
|
|
(if (unspecified? (car values))
|
|
'()
|
|
(map (lambda (value)
|
|
(with-output-to-string (lambda () (write value))))
|
|
values)))
|
|
;; Now do evaluation.
|
|
(let ((intro (if part
|
|
(format #f ";;; Evaluating expression ~A" part)
|
|
";;; Evaluating"))
|
|
(value #f))
|
|
(let* ((do-eval (if m
|
|
(lambda ()
|
|
(display intro)
|
|
(display " in module ")
|
|
(write (module-name m))
|
|
(newline)
|
|
(set! value
|
|
(call-with-values (lambda ()
|
|
(start-stack 'gds-eval-stack
|
|
(eval x m)))
|
|
value-consumer)))
|
|
(lambda ()
|
|
(display intro)
|
|
(display " in current module ")
|
|
(write (module-name (current-module)))
|
|
(newline)
|
|
(set! value
|
|
(call-with-values (lambda ()
|
|
(start-stack 'gds-eval-stack
|
|
(primitive-eval x)))
|
|
value-consumer)))))
|
|
(output
|
|
(with-output-to-string
|
|
(lambda ()
|
|
(catch #t
|
|
(lambda ()
|
|
(lazy-catch #t
|
|
do-eval
|
|
save-lazy-trap-context-and-rethrow))
|
|
(lambda (key . args)
|
|
(case key
|
|
((misc-error signal unbound-variable numerical-overflow)
|
|
(apply display-error #f
|
|
(current-output-port) args)
|
|
(set! value '("error-in-evaluation")))
|
|
(else
|
|
(display "EXCEPTION: ")
|
|
(display key)
|
|
(display " ")
|
|
(write args)
|
|
(newline)
|
|
(set! value
|
|
'("unhandled-exception-in-evaluation"))))))))))
|
|
(list output value))))
|
|
|
|
(define last-lazy-trap-context #f)
|
|
|
|
(define (save-lazy-trap-context-and-rethrow key . args)
|
|
(set! last-lazy-trap-context
|
|
(throw->trap-context key args save-lazy-trap-context-and-rethrow))
|
|
(apply throw key args))
|
|
|
|
(define (run-utility)
|
|
(connect-to-gds)
|
|
(write (getpid))
|
|
(newline)
|
|
(force-output)
|
|
(named-module-use! '(guile-user) '(ice-9 session))
|
|
(gds-accept-input #f))
|
|
|
|
(define-method (trap-description (trap <trap>))
|
|
(let loop ((description (list (class-name (class-of trap))))
|
|
(next 'installed?))
|
|
(case next
|
|
((installed?)
|
|
(loop (if (slot-ref trap 'installed)
|
|
(cons 'installed description)
|
|
description)
|
|
'conditional?))
|
|
((conditional?)
|
|
(loop (if (slot-ref trap 'condition)
|
|
(cons 'conditional description)
|
|
description)
|
|
'skip-count))
|
|
((skip-count)
|
|
(loop (let ((skip-count (slot-ref trap 'skip-count)))
|
|
(if (zero? skip-count)
|
|
description
|
|
(cons* skip-count 'skip-count description)))
|
|
'single-shot?))
|
|
((single-shot?)
|
|
(loop (if (slot-ref trap 'single-shot)
|
|
(cons 'single-shot description)
|
|
description)
|
|
'done))
|
|
(else
|
|
(reverse! description)))))
|
|
|
|
(define-method (trap-description (trap <procedure-trap>))
|
|
(let ((description (next-method)))
|
|
(set-cdr! description
|
|
(cons (procedure-name (slot-ref trap 'procedure))
|
|
(cdr description)))
|
|
description))
|
|
|
|
(define-method (trap-description (trap <source-trap>))
|
|
(let ((description (next-method)))
|
|
(set-cdr! description
|
|
(cons (format #f "~s" (slot-ref trap 'expression))
|
|
(cdr description)))
|
|
description))
|
|
|
|
(define-method (trap-description (trap <location-trap>))
|
|
(let ((description (next-method)))
|
|
(set-cdr! description
|
|
(cons* (slot-ref trap 'file-regexp)
|
|
(slot-ref trap 'line)
|
|
(slot-ref trap 'column)
|
|
(cdr description)))
|
|
description))
|
|
|
|
(define (gds-trace-trap trap-context)
|
|
(connect-to-gds)
|
|
(gds-do-trace trap-context)
|
|
(at-exit (tc:depth trap-context) gds-do-trace))
|
|
|
|
(define (gds-do-trace trap-context)
|
|
(write-form (list 'trace
|
|
(format #f
|
|
"~3@a: ~a"
|
|
(trace/stack-real-depth trap-context)
|
|
(trace/info trap-context)))))
|
|
|
|
(define (gds-trace-subtree trap-context)
|
|
(connect-to-gds)
|
|
(gds-do-trace trap-context)
|
|
(let ((step-trap (make <step-trap> #:behaviour gds-do-trace)))
|
|
(install-trap step-trap)
|
|
(at-exit (tc:depth trap-context)
|
|
(lambda (trap-context)
|
|
(uninstall-trap step-trap)))))
|
|
|
|
;;; (ice-9 gds-client) ends here.
|