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
(cond ((string>=? (version) "1.7")
17
(use-modules (ice-9 debugger utils)))
19
(define the-ice-9-debugger-module (resolve-module '(ice-9 debugger)))
20
(module-export! the-ice-9-debugger-module
22
write-frame-short/application
23
write-frame-short/expression
27
(use-modules (ice-9 debugger))
31
;; Return an integer that somehow identifies the current thread.
32
(define (get-thread-id)
33
(let ((root (dynamic-root)))
34
(cond ((integer? root)
37
(object-address root))
39
(error "Unexpected dynamic root:" root)))))
41
;; gds-debug-read is a high-priority read. The (debug-thread-id ID)
42
;; form causes the frontend to dismiss any reads from threads whose id
43
;; is not ID, until it receives the (thread-id ...) form with the same
44
;; id as ID. Dismissing the reads of any other threads (by sending a
45
;; form that is otherwise ignored) causes those threads to release the
46
;; read mutex, which allows the (gds-read) here to proceed.
47
(define (gds-debug-read)
48
(write-form `(debug-thread-id ,(get-thread-id)))
51
(define (gds-debug-trap trap-context)
52
"Invoke the GDS debugger to explore the stack at the specified trap."
54
(start-stack 'debugger
55
(let* ((stack (tc:stack trap-context))
56
(flags1 (let ((trap-type (tc:type trap-context)))
60
(tc:return-value trap-context)))
63
(flags (if (tc:continuation trap-context)
64
(cons #:continuable flags1)
66
(fired-traps (tc:fired-traps trap-context))
67
(special-index (and (= (length fired-traps) 1)
68
(is-a? (car fired-traps) <exit-trap>)
69
(eq? (tc:type trap-context) #:return)
70
(- (tc:depth trap-context)
71
(slot-ref (car fired-traps) 'depth)))))
72
;; Write current stack to the frontend.
73
(write-form (list 'stack
74
(if (and special-index (> special-index 0))
77
(stack->emacs-readable stack)
78
(append (flags->emacs-readable flags)
79
(slot-ref trap-context
80
'handler-return-syms))))
81
;; Now wait for instruction.
82
(let loop ((protocol (gds-debug-read)))
86
;; Request to tweak the handler return value.
87
(let ((tweaking (catch #t
89
(list (with-input-from-string
92
(lambda ignored #f))))
94
(slot-set! trap-context
96
(cons 'instead (car tweaking)))))
97
(loop (gds-debug-read)))
99
;; Continue (by exiting the debugger).
102
;; Evaluate expression in specified frame.
103
(eval-in-frame stack (cadr protocol) (caddr protocol))
104
(loop (gds-debug-read)))
106
;; Return frame info.
107
(let ((frame (stack-ref stack (cadr protocol))))
108
(write-form (list 'info-result
109
(with-output-to-string
111
(write-frame-long frame))))))
112
(loop (gds-debug-read)))
114
;; Return frame args.
115
(let ((frame (stack-ref stack (cadr protocol))))
116
(write-form (list 'info-result
117
(with-output-to-string
119
(write-frame-args-long frame))))))
120
(loop (gds-debug-read)))
122
;; Show source of application procedure.
123
(let* ((frame (stack-ref stack (cadr protocol)))
124
(proc (frame-procedure frame))
125
(source (and proc (procedure-source proc))))
126
(write-form (list 'info-result
128
(sans-surrounding-whitespace
129
(with-output-to-string
131
(pretty-print source))))
133
"This procedure is coded in C"
134
"This frame has no procedure")))))
135
(loop (gds-debug-read)))
137
;; Show the traps that fired here.
138
(write-form (list 'info-result
139
(with-output-to-string
142
(tc:fired-traps trap-context))))))
143
(loop (gds-debug-read)))
145
;; Set temporary breakpoint on next trap.
146
(at-step gds-debug-trap
149
(if (memq #:return flags)
151
(- (stack-length stack)
154
;; Set temporary breakpoint on exit from
156
(at-exit (- (stack-length stack) (cadr protocol))
159
;; Set temporary breakpoint on next trap in same
161
(at-step gds-debug-trap
163
(frame-file-name (stack-ref stack
165
(if (memq #:return flags)
167
(- (stack-length stack)
170
(safely-handle-nondebug-protocol protocol)
171
(loop (gds-debug-read))))))))
173
(define (connect-to-gds . application-name)
177
(or (let ((s (socket PF_INET SOCK_STREAM 0))
180
(setsockopt s SOL_TCP TCP_NODELAY 1)
183
(connect s AF_INET (inet-aton "127.0.0.1") 8333)
186
(let ((s (socket PF_UNIX SOCK_STREAM 0)))
189
(connect s AF_UNIX "/tmp/.gds_socket")
192
(error "Couldn't connect to GDS by TCP or Unix domain socket")))
193
(write-form (list 'name (getpid) (apply client-name application-name))))))
195
(define (client-name . application-name)
196
(let loop ((args (append application-name (program-arguments))))
198
(format #f "PID ~A" (getpid))
199
(let ((arg (car args)))
200
(cond ((string-match "^(.*[/\\])?guile(\\..*)?$" arg)
202
((string-match "^-" arg)
205
(format #f "~A (PID ~A)" arg (getpid))))))))
207
(if (not (defined? 'make-mutex))
209
(define (make-mutex) #f)
210
(define lock-mutex noop)
211
(define unlock-mutex noop)))
213
(define write-mutex (make-mutex))
215
(define (write-form form)
216
;; Write any form FORM to GDS.
217
(lock-mutex write-mutex)
218
(write form gds-port)
220
(force-output gds-port)
221
(unlock-mutex write-mutex))
223
(define (stack->emacs-readable stack)
224
;; Return Emacs-readable representation of STACK.
226
(frame->emacs-readable (stack-ref stack index)))
227
(iota (min (stack-length stack)
228
(cadr (memq 'depth (debug-options)))))))
230
(define (frame->emacs-readable frame)
231
;; Return Emacs-readable representation of FRAME.
232
(if (frame-procedure? frame)
234
(with-output-to-string
236
(display (if (frame-real? frame) " " "t "))
237
(write-frame-short/application frame)))
238
(source->emacs-readable frame))
240
(with-output-to-string
242
(display (if (frame-real? frame) " " "t "))
243
(write-frame-short/expression frame)))
244
(source->emacs-readable frame))))
246
(define (source->emacs-readable frame)
247
;; Return Emacs-readable representation of the filename, line and
248
;; column source properties of SOURCE.
249
(or (frame->source-position frame) 'nil))
251
(define (flags->emacs-readable flags)
252
;; Return Emacs-readable representation of trap FLAGS.
255
(let ((erf (if (and (keyword? flag)
256
(not (eq? prev #:return)))
257
(keyword->symbol flag)
258
(format #f "~S" flag))))
263
(define (eval-in-frame stack index expr)
269
(local-eval (with-input-from-string expr read)
270
(memoized-environment
271
(frame-source (stack-ref stack
274
(cons 'ERROR args)))))))
276
(set! (behaviour-ordering gds-debug-trap) 100)
278
;;; Code below here adds support for interaction between the GDS
279
;;; client program and the Emacs frontend even when not stopped in the
282
;; A mutex to control attempts by multiple threads to read protocol
283
;; back from the frontend.
284
(define gds-read-mutex (make-mutex))
286
;; Read a protocol instruction from the frontend.
288
;; Acquire the read mutex.
289
(lock-mutex gds-read-mutex)
290
;; Tell the front end something that identifies us as a thread.
291
(write-form `(thread-id ,(get-thread-id)))
292
;; Now read, then release the mutex and return what was read.
294
(lambda () (read gds-port))
295
(lambda ignored the-eof-object))))
296
(unlock-mutex gds-read-mutex)
299
(define (gds-accept-input exit-on-continue)
300
;; If reading from the GDS connection returns EOF, we will throw to
304
(let loop ((protocol (gds-read)))
305
(if (or (eof-object? protocol)
306
(and exit-on-continue
307
(eq? (car protocol) 'continue)))
309
(safely-handle-nondebug-protocol protocol)
311
(lambda ignored #f)))
313
(define (safely-handle-nondebug-protocol protocol)
314
;; This catch covers any internal errors in the GDS code or
320
(handle-nondebug-protocol protocol))
321
save-lazy-trap-context-and-rethrow))
324
`(eval-results (error . ,(format #f "~s" protocol))
325
,(if last-lazy-trap-context 't 'nil)
327
Please report this to <neil@ossau.uklinux.net>, ideally including:
328
- a description of the scenario in which this error occurred
329
- which versions of Guile and guile-debugging you are using
330
- the error stack, which you can get by clicking on the link below,
331
and then cut and paste into your report.
333
,(list (with-output-to-string
340
;; The key that is used to signal a read error changes from 1.6 to
341
;; 1.8; here we cover all eventualities by discovering the key
343
(define read-error-key
346
(with-input-from-string "(+ 3 4" read))
350
(define (handle-nondebug-protocol protocol)
354
(set! last-lazy-trap-context #f)
355
(apply (lambda (correlator module port-name line column code)
356
(with-input-from-string code
358
(set-port-filename! (current-input-port) port-name)
359
(set-port-line! (current-input-port) line)
360
(set-port-column! (current-input-port) column)
361
(let ((m (and module (resolve-module-from-root module))))
362
(catch read-error-key
364
(let loop ((exprs '()) (x (read)))
366
;; Expressions to be evaluated have all
367
;; been read. Now evaluate them.
368
(let loop2 ((exprs (reverse! exprs))
372
(write-form `(eval-results ,correlator
373
,(if last-lazy-trap-context 't 'nil)
376
(append results (gds-eval (car exprs) m
377
(if (and (null? (cdr exprs))
381
;; Another complete expression read; add
383
(loop (cons x exprs) (read)))))
385
(write-form `(eval-results
387
,(if last-lazy-trap-context 't 'nil)
388
,(with-output-to-string
390
(display ";;; Reading expressions")
391
(display " to evaluate\n")
392
(apply display-error #f
393
(current-output-port) args)))
394
("error-in-read")))))))))
398
(let ((matches (apropos-internal
399
(string-append "^" (regexp-quote (cadr protocol))))))
400
(cond ((null? matches)
401
(write-form '(completion-result nil)))
403
;;(write matches (current-error-port))
404
;;(newline (current-error-port))
406
(let loop ((match (symbol->string (car matches)))
407
(matches (cdr matches)))
408
;;(write match (current-error-port))
409
;;(newline (current-error-port))
410
;;(write matches (current-error-port))
411
;;(newline (current-error-port))
414
(if (string-prefix=? match
415
(symbol->string (car matches)))
416
(loop match (cdr matches))
417
(loop (substring match 0
418
(- (string-length match) 1))
420
(if (string=? match (cadr protocol))
421
(write-form `(completion-result
422
,(map symbol->string matches)))
423
(write-form `(completion-result
426
((debug-lazy-trap-context)
427
(if last-lazy-trap-context
428
(gds-debug-trap last-lazy-trap-context)
429
(error "There is no stack available to show")))
432
(error "Unexpected protocol:" protocol))))
434
(define (resolve-module-from-root name)
435
(save-module-excursion
437
(set-current-module the-root-module)
438
(resolve-module name))))
440
(define (gds-eval x m part)
441
;; Consumer to accept possibly multiple values and present them for
442
;; Emacs as a list of strings.
443
(define (value-consumer . values)
444
(if (unspecified? (car values))
447
(with-output-to-string (lambda () (write value))))
449
;; Now do evaluation.
450
(let ((intro (if part
451
(format #f ";;; Evaluating expression ~A" part)
454
(let* ((do-eval (if m
457
(display " in module ")
458
(write (module-name m))
461
(call-with-values (lambda ()
462
(start-stack 'gds-eval-stack
467
(display " in current module ")
468
(write (module-name (current-module)))
471
(call-with-values (lambda ()
472
(start-stack 'gds-eval-stack
476
(with-output-to-string
482
save-lazy-trap-context-and-rethrow))
485
((misc-error signal unbound-variable numerical-overflow)
486
(apply display-error #f
487
(current-output-port) args)
488
(set! value '("error-in-evaluation")))
490
(display "EXCEPTION: ")
496
'("unhandled-exception-in-evaluation"))))))))))
497
(list output value))))
499
(define last-lazy-trap-context #f)
501
(define (save-lazy-trap-context-and-rethrow key . args)
502
(set! last-lazy-trap-context
503
(throw->trap-context key args save-lazy-trap-context-and-rethrow))
504
(apply throw key args))
506
(define (run-utility)
511
(named-module-use! '(guile-user) '(ice-9 session))
512
(gds-accept-input #f))
514
(define-method (trap-description (trap <trap>))
515
(let loop ((description (list (class-name (class-of trap))))
519
(loop (if (slot-ref trap 'installed)
520
(cons 'installed description)
524
(loop (if (slot-ref trap 'condition)
525
(cons 'conditional description)
529
(loop (let ((skip-count (slot-ref trap 'skip-count)))
530
(if (zero? skip-count)
532
(cons* skip-count 'skip-count description)))
535
(loop (if (slot-ref trap 'single-shot)
536
(cons 'single-shot description)
540
(reverse! description)))))
542
(define-method (trap-description (trap <procedure-trap>))
543
(let ((description (next-method)))
544
(set-cdr! description
545
(cons (procedure-name (slot-ref trap 'procedure))
549
(define-method (trap-description (trap <source-trap>))
550
(let ((description (next-method)))
551
(set-cdr! description
552
(cons (format #f "~s" (slot-ref trap 'expression))
556
(define-method (trap-description (trap <location-trap>))
557
(let ((description (next-method)))
558
(set-cdr! description
559
(cons* (slot-ref trap 'file-regexp)
560
(slot-ref trap 'line)
561
(slot-ref trap 'column)
565
(define (gds-trace-trap trap-context)
567
(gds-do-trace trap-context)
568
(at-exit (tc:depth trap-context) gds-do-trace))
570
(define (gds-do-trace trap-context)
571
(write-form (list 'trace
574
(trace/stack-real-depth trap-context)
575
(trace/info trap-context)))))
577
(define (gds-trace-subtree trap-context)
579
(gds-do-trace trap-context)
580
(let ((step-trap (make <step-trap> #:behaviour gds-do-trace)))
581
(install-trap step-trap)
582
(at-exit (tc:depth trap-context)
583
(lambda (trap-context)
584
(uninstall-trap step-trap)))))
586
;;; (ice-9 gds-client) ends here.