1 (define-module (ice-9 gds-client)
2 #:use-module (oop goops)
3 #:use-module (oop goops describe)
4 #:use-module (ice-9 debugging trace)
5 #:use-module (ice-9 debugging traps)
6 #:use-module (ice-9 debugging trc)
7 #:use-module (ice-9 debugging steps)
8 #:use-module (ice-9 pretty-print)
9 #:use-module (ice-9 regex)
10 #:use-module (ice-9 session)
11 #:use-module (ice-9 string-fun)
12 #:export (gds-debug-trap
16 (use-modules (ice-9 debugger utils))
18 (use-modules (ice-9 debugger))
22 ;; Return an integer that somehow identifies the current thread.
23 (define (get-thread-id)
24 (let ((root (dynamic-root)))
25 (cond ((integer? root)
28 (object-address root))
30 (error "Unexpected dynamic root:" root)))))
32 ;; gds-debug-read is a high-priority read. The (debug-thread-id ID)
33 ;; form causes the frontend to dismiss any reads from threads whose id
34 ;; is not ID, until it receives the (thread-id ...) form with the same
35 ;; id as ID. Dismissing the reads of any other threads (by sending a
36 ;; form that is otherwise ignored) causes those threads to release the
37 ;; read mutex, which allows the (gds-read) here to proceed.
38 (define (gds-debug-read)
39 (write-form `(debug-thread-id ,(get-thread-id)))
42 (define (gds-debug-trap trap-context)
43 "Invoke the GDS debugger to explore the stack at the specified trap."
45 (start-stack 'debugger
46 (let* ((stack (tc:stack trap-context))
47 (flags1 (let ((trap-type (tc:type trap-context)))
51 (tc:return-value trap-context)))
54 (flags (if (tc:continuation trap-context)
55 (cons #:continuable flags1)
57 (fired-traps (tc:fired-traps trap-context))
58 (special-index (and (= (length fired-traps) 1)
59 (is-a? (car fired-traps) <exit-trap>)
60 (eq? (tc:type trap-context) #:return)
61 (- (tc:depth trap-context)
62 (slot-ref (car fired-traps) 'depth)))))
63 ;; Write current stack to the frontend.
64 (write-form (list 'stack
65 (if (and special-index (> special-index 0))
68 (stack->emacs-readable stack)
69 (append (flags->emacs-readable flags)
70 (slot-ref trap-context
71 'handler-return-syms))))
72 ;; Now wait for instruction.
73 (let loop ((protocol (gds-debug-read)))
77 ;; Request to tweak the handler return value.
78 (let ((tweaking (catch #t
80 (list (with-input-from-string
83 (lambda ignored #f))))
85 (slot-set! trap-context
87 (cons 'instead (car tweaking)))))
88 (loop (gds-debug-read)))
90 ;; Continue (by exiting the debugger).
93 ;; Evaluate expression in specified frame.
94 (eval-in-frame stack (cadr protocol) (caddr protocol))
95 (loop (gds-debug-read)))
98 (let ((frame (stack-ref stack (cadr protocol))))
99 (write-form (list 'info-result
100 (with-output-to-string
102 (write-frame-long frame))))))
103 (loop (gds-debug-read)))
105 ;; Return frame args.
106 (let ((frame (stack-ref stack (cadr protocol))))
107 (write-form (list 'info-result
108 (with-output-to-string
110 (write-frame-args-long frame))))))
111 (loop (gds-debug-read)))
113 ;; Show source of application procedure.
114 (let* ((frame (stack-ref stack (cadr protocol)))
115 (proc (frame-procedure frame))
116 (source (and proc (procedure-source proc))))
117 (write-form (list 'info-result
119 (sans-surrounding-whitespace
120 (with-output-to-string
122 (pretty-print source))))
124 "This procedure is coded in C"
125 "This frame has no procedure")))))
126 (loop (gds-debug-read)))
128 ;; Show the traps that fired here.
129 (write-form (list 'info-result
130 (with-output-to-string
133 (tc:fired-traps trap-context))))))
134 (loop (gds-debug-read)))
136 ;; Set temporary breakpoint on next trap.
137 (at-step gds-debug-trap
140 (if (memq #:return flags)
142 (- (stack-length stack)
145 ;; Set temporary breakpoint on exit from
147 (at-exit (- (stack-length stack) (cadr protocol))
150 ;; Set temporary breakpoint on next trap in same
152 (at-step gds-debug-trap
154 (frame-file-name (stack-ref stack
156 (if (memq #:return flags)
158 (- (stack-length stack)
161 (safely-handle-nondebug-protocol protocol)
162 (loop (gds-debug-read))))))))
164 (define (connect-to-gds . application-name)
166 (let ((gds-unix-socket-name (getenv "GDS_UNIX_SOCKET_NAME")))
168 (or (and gds-unix-socket-name
170 (let ((s (socket PF_UNIX SOCK_STREAM 0)))
171 (connect s AF_UNIX gds-unix-socket-name)
174 (let ((s (socket PF_INET SOCK_STREAM 0))
177 (setsockopt s SOL_TCP TCP_NODELAY 1)
178 (connect s AF_INET (inet-aton "127.0.0.1") 8333)
180 (error "Couldn't connect to GDS by TCP or Unix domain socket")))
181 (write-form (list 'name (getpid) (apply client-name application-name))))))
183 (define (client-name . application-name)
184 (let loop ((args (append application-name (program-arguments))))
186 (format #f "PID ~A" (getpid))
187 (let ((arg (car args)))
188 (cond ((string-match "^(.*[/\\])?guile(\\..*)?$" arg)
190 ((string-match "^-" arg)
193 (format #f "~A (PID ~A)" arg (getpid))))))))
195 ;;(if (not (defined? 'make-mutex))
197 ;; (define (make-mutex) #f)
198 ;; (define lock-mutex noop)
199 ;; (define unlock-mutex noop)))
201 (define write-mutex (make-mutex))
203 (define (write-form form)
204 ;; Write any form FORM to GDS.
205 (lock-mutex write-mutex)
206 (write form gds-port)
208 (force-output gds-port)
209 (unlock-mutex write-mutex))
211 (define (stack->emacs-readable stack)
212 ;; Return Emacs-readable representation of STACK.
214 (frame->emacs-readable (stack-ref stack index)))
215 (iota (min (stack-length stack)
216 (cadr (memq 'depth (debug-options)))))))
218 (define (frame->emacs-readable frame)
219 ;; Return Emacs-readable representation of FRAME.
220 (if (frame-procedure? frame)
222 (with-output-to-string
224 (display (if (frame-real? frame) " " "t "))
225 (write-frame-short/application frame)))
226 (source->emacs-readable frame))
228 (with-output-to-string
230 (display (if (frame-real? frame) " " "t "))
231 (write-frame-short/expression frame)))
232 (source->emacs-readable frame))))
234 (define (source->emacs-readable frame)
235 ;; Return Emacs-readable representation of the filename, line and
236 ;; column source properties of SOURCE.
237 (or (frame->source-position frame) 'nil))
239 (define (flags->emacs-readable flags)
240 ;; Return Emacs-readable representation of trap FLAGS.
243 (let ((erf (if (and (keyword? flag)
244 (not (eq? prev #:return)))
245 (keyword->symbol flag)
246 (format #f "~S" flag))))
251 ;; FIXME: the new evaluator breaks this, by removing local-eval. Need to
252 ;; figure out our story in this regard.
253 (define (eval-in-frame stack index expr)
259 (local-eval (with-input-from-string expr read)
260 (memoized-environment
261 (frame-source (stack-ref stack
264 (cons 'ERROR args)))))))
266 (set! (behaviour-ordering gds-debug-trap) 100)
268 ;;; Code below here adds support for interaction between the GDS
269 ;;; client program and the Emacs frontend even when not stopped in the
272 ;; A mutex to control attempts by multiple threads to read protocol
273 ;; back from the frontend.
274 (define gds-read-mutex (make-mutex))
276 ;; Read a protocol instruction from the frontend.
278 ;; Acquire the read mutex.
279 (lock-mutex gds-read-mutex)
280 ;; Tell the front end something that identifies us as a thread.
281 (write-form `(thread-id ,(get-thread-id)))
282 ;; Now read, then release the mutex and return what was read.
284 (lambda () (read gds-port))
285 (lambda ignored the-eof-object))))
286 (unlock-mutex gds-read-mutex)
289 (define (gds-accept-input exit-on-continue)
290 ;; If reading from the GDS connection returns EOF, we will throw to
294 (let loop ((protocol (gds-read)))
295 (if (or (eof-object? protocol)
296 (and exit-on-continue
297 (eq? (car protocol) 'continue)))
299 (safely-handle-nondebug-protocol protocol)
301 (lambda ignored #f)))
303 (define (safely-handle-nondebug-protocol protocol)
304 ;; This catch covers any internal errors in the GDS code or
310 (handle-nondebug-protocol protocol))
311 save-lazy-trap-context-and-rethrow))
314 `(eval-results (error . ,(format #f "~s" protocol))
315 ,(if last-lazy-trap-context 't 'nil)
317 Please report this to <neil@ossau.uklinux.net>, ideally including:
318 - a description of the scenario in which this error occurred
319 - which versions of Guile and guile-debugging you are using
320 - the error stack, which you can get by clicking on the link below,
321 and then cut and paste into your report.
323 ,(list (with-output-to-string
330 ;; The key that is used to signal a read error changes from 1.6 to
331 ;; 1.8; here we cover all eventualities by discovering the key
333 (define read-error-key
336 (with-input-from-string "(+ 3 4" read))
340 (define (handle-nondebug-protocol protocol)
344 (set! last-lazy-trap-context #f)
345 (apply (lambda (correlator module port-name line column code flags)
346 (with-input-from-string code
348 (set-port-filename! (current-input-port) port-name)
349 (set-port-line! (current-input-port) line)
350 (set-port-column! (current-input-port) column)
351 (let ((m (and module (resolve-module-from-root module))))
352 (catch read-error-key
354 (let loop ((exprs '()) (x (read)))
356 ;; Expressions to be evaluated have all
357 ;; been read. Now evaluate them.
358 (let loop2 ((exprs (reverse! exprs))
362 (write-form `(eval-results ,correlator
363 ,(if last-lazy-trap-context 't 'nil)
366 (append results (gds-eval (car exprs) m
367 (if (and (null? (cdr exprs))
371 ;; Another complete expression read; add
376 (install-trap (make <source-trap>
378 #:behaviour gds-debug-trap)))
379 (loop (cons x exprs) (read))))))
381 (write-form `(eval-results
383 ,(if last-lazy-trap-context 't 'nil)
384 ,(with-output-to-string
386 (display ";;; Reading expressions")
387 (display " to evaluate\n")
388 (apply display-error #f
389 (current-output-port) args)))
390 ("error-in-read")))))))))
394 (let ((matches (apropos-internal
395 (string-append "^" (regexp-quote (cadr protocol))))))
396 (cond ((null? matches)
397 (write-form '(completion-result nil)))
399 ;;(write matches (current-error-port))
400 ;;(newline (current-error-port))
402 (let loop ((match (symbol->string (car matches)))
403 (matches (cdr matches)))
404 ;;(write match (current-error-port))
405 ;;(newline (current-error-port))
406 ;;(write matches (current-error-port))
407 ;;(newline (current-error-port))
410 (if (string-prefix=? match
411 (symbol->string (car matches)))
412 (loop match (cdr matches))
413 (loop (substring match 0
414 (- (string-length match) 1))
416 (if (string=? match (cadr protocol))
417 (write-form `(completion-result
418 ,(map symbol->string matches)))
419 (write-form `(completion-result
422 ((debug-lazy-trap-context)
423 (if last-lazy-trap-context
424 (gds-debug-trap last-lazy-trap-context)
425 (error "There is no stack available to show")))
428 (error "Unexpected protocol:" protocol))))
430 (define (resolve-module-from-root name)
431 (save-module-excursion
433 (set-current-module the-root-module)
434 (resolve-module name))))
436 (define (gds-eval x m part)
437 ;; Consumer to accept possibly multiple values and present them for
438 ;; Emacs as a list of strings.
439 (define (value-consumer . values)
440 (if (unspecified? (car values))
443 (with-output-to-string (lambda () (write value))))
445 ;; Now do evaluation.
446 (let ((intro (if part
447 (format #f ";;; Evaluating expression ~A" part)
450 (let* ((do-eval (if m
453 (display " in module ")
454 (write (module-name m))
457 (call-with-values (lambda ()
458 (start-stack 'gds-eval-stack
463 (display " in current module ")
464 (write (module-name (current-module)))
467 (call-with-values (lambda ()
468 (start-stack 'gds-eval-stack
472 (with-output-to-string
478 save-lazy-trap-context-and-rethrow))
481 ((misc-error signal unbound-variable numerical-overflow)
482 (apply display-error #f
483 (current-output-port) args)
484 (set! value '("error-in-evaluation")))
486 (display "EXCEPTION: ")
492 '("unhandled-exception-in-evaluation"))))))))))
493 (list output value))))
495 (define last-lazy-trap-context #f)
497 (define (save-lazy-trap-context-and-rethrow key . args)
498 (set! last-lazy-trap-context
499 (throw->trap-context key args save-lazy-trap-context-and-rethrow))
500 (apply throw key args))
502 (define (run-utility)
507 (named-module-use! '(guile-user) '(ice-9 session))
508 (gds-accept-input #f))
510 (define-method (trap-description (trap <trap>))
511 (let loop ((description (list (class-name (class-of trap))))
515 (loop (if (slot-ref trap 'installed)
516 (cons 'installed description)
520 (loop (if (slot-ref trap 'condition)
521 (cons 'conditional description)
525 (loop (let ((skip-count (slot-ref trap 'skip-count)))
526 (if (zero? skip-count)
528 (cons* skip-count 'skip-count description)))
531 (loop (if (slot-ref trap 'single-shot)
532 (cons 'single-shot description)
536 (reverse! description)))))
538 (define-method (trap-description (trap <procedure-trap>))
539 (let ((description (next-method)))
540 (set-cdr! description
541 (cons (procedure-name (slot-ref trap 'procedure))
545 (define-method (trap-description (trap <source-trap>))
546 (let ((description (next-method)))
547 (set-cdr! description
548 (cons (format #f "~s" (slot-ref trap 'expression))
552 (define-method (trap-description (trap <location-trap>))
553 (let ((description (next-method)))
554 (set-cdr! description
555 (cons* (slot-ref trap 'file-regexp)
556 (slot-ref trap 'line)
557 (slot-ref trap 'column)
561 (define (gds-trace-trap trap-context)
563 (gds-do-trace trap-context)
564 (at-exit (tc:depth trap-context) gds-do-trace))
566 (define (gds-do-trace trap-context)
567 (write-form (list 'trace
570 (trace/stack-real-depth trap-context)
571 (trace/info trap-context)))))
573 (define (gds-trace-subtree trap-context)
575 (gds-do-trace trap-context)
576 (let ((step-trap (make <step-trap> #:behaviour gds-do-trace)))
577 (install-trap step-trap)
578 (at-exit (tc:depth trap-context)
579 (lambda (trap-context)
580 (uninstall-trap step-trap)))))
582 ;;; (ice-9 gds-client) ends here.