1 ;;;; Guile Debugger UI client
3 ;;; Copyright (C) 2003, 2004 Free Software Foundation, Inc.
5 ;; This library is free software; you can redistribute it and/or
6 ;; modify it under the terms of the GNU Lesser General Public
7 ;; License as published by the Free Software Foundation; either
8 ;; version 2.1 of the License, or (at your option) any later version.
10 ;; This library is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; Lesser General Public License for more details.
15 ;; You should have received a copy of the GNU Lesser General Public
16 ;; License along with this library; if not, write to the Free Software
17 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 (define-module (emacs gds-client)
20 #:use-module (ice-9 debugger)
21 #:use-module (ice-9 debugger behaviour)
22 #:use-module (ice-9 debugger breakpoints)
23 #:use-module (ice-9 debugger breakpoints procedural)
24 #:use-module (ice-9 debugger breakpoints source)
25 #:use-module (ice-9 debugger state)
26 #:use-module (ice-9 debugger trap-hooks)
27 #:use-module (ice-9 debugger utils)
28 #:use-module (ice-9 optargs)
29 #:use-module (ice-9 regex)
30 #:use-module (ice-9 session)
31 #:use-module (ice-9 string-fun)
32 #:use-module (ice-9 threads)
33 #:export (gds-port-number
41 ;;;; {Internal Tracing and Debugging}
43 ;; Some of this module's thread and mutex code is quite tricky and
44 ;; includes `trc' statements to trace out useful information if the
45 ;; environment variable GDS_TRC is defined.
47 (if (getenv "GDS_TRC")
48 (let ((port (open-output-file "/home/neil/gds-client.log"))
49 (trc-mutex (make-mutex)))
54 (force-output port))))
57 (define-macro (assert expr)
59 (error "Assertion failed" expr)))
64 ;; Communication between this module (running in the application being
65 ;; debugged) and the GDS server and UI code (running in/under Emacs)
66 ;; is through a TCP connection. `gds-port-number' is the TCP port
67 ;; number where the server listens for application connections.
68 (define gds-port-number 8333)
70 ;; Once connected, the TCP socket port to the server.
73 ;; Public procedure to discover whether there is a GDS connection yet.
74 (define (gds-connected?)
75 "Return @code{#t} if a UI server connected has been made; else @code{#f}."
78 ;; Public procedure to create the connection to the GDS server.
79 (define* (gds-connect name #:optional host)
80 "Connect to the GDS server as @var{name}, a string that should be
81 sufficient to describe the calling application to the GDS frontend
82 user. The optional @var{host} arg specifies the hostname or dotted
83 decimal IP address where the UI server is running; default is
86 (error "Already connected to UI server!"))
87 ;; Connect to debug server.
89 (let ((s (socket PF_INET SOCK_STREAM 0))
92 (setsockopt s SOL_TCP TCP_NODELAY 1)
93 (connect s AF_INET (inet-aton (or host "127.0.0.1")) gds-port-number)
95 ;; Set debugger-output-port so that messages written to it are not
96 ;; displayed on the application's stdout, but instead accumulated
97 ;; for sending to the GDS frontend.
98 (set! (debugger-output-port)
99 (make-soft-port (vector accumulate-output
103 ;; Announce ourselves to the server.
104 (write-form (list 'name name (getpid)))
105 (add-trapped-stack-id! 'gds-eval-stack)
106 ;; Start the UI read thread.
107 (set! ui-read-thread (make-thread ui-read-thread-proc)))
109 (define accumulated-output '())
111 (define (accumulate-output obj)
112 (set! accumulated-output
113 (cons (if (string? obj) obj (make-string 1 obj))
114 accumulated-output)))
116 (define (get-accumulated-output)
117 (let ((s (apply string-append (reverse! accumulated-output))))
118 (set! accumulated-output '())
122 ;;;; {UI Read Thread}
124 ;; Except when the application enters the debugger, communication with
125 ;; the GDS server and frontend is managed by a dedicated thread for
126 ;; this purpose. This design avoids having to modify application code
127 ;; at the expense of requiring a Guile with threads support.
128 (define (ui-read-thread-proc)
129 (write-status 'running)
130 (let ((eval-thread-needed? #t))
131 ;; Start up the default eval thread.
132 (make-thread eval-thread 1 (lambda () (not eval-thread-needed?)))
133 (with-mutex ui-read-mutex
135 ;; Protected thunk: loop reading either protocol input from
136 ;; the server, or an indication (through ui-read-switch-pipe)
137 ;; that a thread in the debugger wants to take over the
138 ;; interaction with the server.
140 (let loop ((avail '()))
141 (write-note 'startloop)
142 (cond ((not gds-port)) ; exit loop
144 (write-status 'ready-for-input)
145 (loop (without-mutex ui-read-mutex
146 (car (select (list gds-port
147 (car ui-read-switch-pipe))
150 (write-note 'sthg-to-read)
151 (let ((port (car avail)))
152 (if (eq? port gds-port)
153 (handle-instruction #f (read gds-port))
155 (write-note 'debugger-takeover)
156 ;; Notification from debugger that it wants
157 ;; to take over. Read the notification
159 (read-char (car ui-read-switch-pipe))
160 ;; Wait on ui-read-switch variable - this
161 ;; allows the debugger thread to grab the
163 (write-note 'cond-wait)
164 (signal-condition-variable ui-read-switch)
165 (wait-condition-variable ui-read-switch
169 (write-note 'loopexited)))
172 ;; Tell the eval thread that it can exit.
173 (with-mutex eval-work-mutex
174 (set! eval-thread-needed? #f)
175 (broadcast-condition-variable eval-work-changed))))
177 ;; It's useful to keep a note of the UI thread's id.
178 (define ui-read-thread #f)
180 ;; Mutex used to control which thread is currently reading the TCP
181 ;; connection to the server/UI.
182 (define ui-read-mutex (make-mutex))
184 ;; Condition variable used by threads interested in reading the TCP
185 ;; connection to signal changes in their state.
186 (define ui-read-switch (make-condition-variable))
188 ;; Pipe used by application threads that enter the debugger to tell
189 ;; the UI read thread that they'd like to take over reading the TCP
191 (define ui-read-switch-pipe (pipe))
194 ;;;; {Debugger Integration}
196 ;; When a thread enters the Guile debugger and a GDS connection is
197 ;; present, the debugger calls `gds-command-loop' instead of entering
198 ;; its usual command loop.
199 (define (gds-command-loop state)
200 "Interact with the UI frontend."
202 (error "Not connected to UI server."))
203 ;; Take over server/UI interaction from the normal UI read thread.
204 (with-mutex ui-read-mutex
205 (write-char #\x (cdr ui-read-switch-pipe))
206 (force-output (cdr ui-read-switch-pipe))
207 (write-note 'char-written)
208 (wait-condition-variable ui-read-switch ui-read-mutex)
209 ;; We now "have the com", as they say on Star Trek.
210 (catch #t ; Only expect here 'exit-debugger or 'server-died.
212 (let loop ((state state))
213 ;; Write accumulated debugger output.
214 (write-form (list 'output (sans-surrounding-whitespace
215 (get-accumulated-output))))
216 ;; Write current state to the frontend.
217 (if state (write-stack state))
218 ;; Tell the frontend that we're waiting for input.
219 (write-status 'waiting-for-input)
220 ;; Read next instruction, act on it, and loop with updated
222 (loop (handle-instruction state (read gds-port)))))
223 (lambda args *unspecified*))
224 (write-note 'cond-signal)
225 ;; Tell the UI read thread that it can take control again.
226 (signal-condition-variable ui-read-switch)))
229 ;;;; {General Output to Server/UI}
232 (let ((protocol-mutex (make-mutex)))
234 ;; Write any form FORM to UI frontend.
235 (with-mutex protocol-mutex
236 (write form gds-port)
238 (force-output gds-port)))))
240 (define (write-note note)
241 ;; Write a note (for debugging this code) to UI frontend.
242 (false-if-exception (write-form `(note ,note))))
244 (define (write-status status)
245 (write-form (list 'current-module
246 (format #f "~S" (module-name (current-module)))))
247 (write-form (list 'status status)))
250 ;;;; {Stack Output to Server/UI}
252 (define (write-stack state)
253 ;; Write Emacs-readable representation of current state to UI
255 (let ((frames (stack->emacs-readable (state-stack state)))
256 (index (index->emacs-readable (state-index state)))
257 (flags (flags->emacs-readable (state-flags state))))
258 (if (memq 'backwards (debug-options))
259 (write-form (list 'stack
263 ;; Calculate (length frames) here because `reverse!' will make
264 ;; the original `frames' invalid.
265 (let ((nframes (length frames)))
266 (write-form (list 'stack
271 (define (stack->emacs-readable stack)
272 ;; Return Emacs-readable representation of STACK.
274 (frame->emacs-readable (stack-ref stack index)))
275 (iota (min (stack-length stack)
276 (cadr (memq 'depth (debug-options)))))))
278 (define (frame->emacs-readable frame)
279 ;; Return Emacs-readable representation of FRAME.
280 (if (frame-procedure? frame)
282 (with-output-to-string
284 (display (if (frame-real? frame) " " "t "))
285 (write-frame-short/application frame)))
286 (source->emacs-readable (or (frame-source frame)
287 (let ((proc (frame-procedure frame)))
289 (procedure-source proc))))))
291 (with-output-to-string
293 (display (if (frame-real? frame) " " "t "))
294 (write-frame-short/expression frame)))
295 (source->emacs-readable (frame-source frame)))))
297 (define (source->emacs-readable source)
298 ;; Return Emacs-readable representation of the filename, line and
299 ;; column source properties of SOURCE.
301 (string? (source-property source 'filename)))
302 (list (source-property source 'filename)
303 (source-property source 'line)
304 (source-property source 'column))
307 (define (index->emacs-readable index)
308 ;; Return Emacs-readable representation of INDEX (the current stack
312 (define (flags->emacs-readable flags)
313 ;; Return Emacs-readable representation of FLAGS passed to
317 (keyword->symbol flag)
318 (format #f "~S" flag)))
322 ;;;; {Handling GDS Protocol Instructions}
324 ;; Instructions from the server/UI always come through here. If
325 ;; `state' is non-#f, we are in the debugger; otherwise, not.
326 (define (handle-instruction state ins)
327 (if (eof-object? ins)
333 (handle-instruction-1 state ins))
335 (set! internal-error-stack (make-stack #t))
336 (apply throw key args))))
340 (apply throw key args))
343 `(eval-results (error . "")
344 "GDS Internal Error\n"
345 ,(list (with-output-to-string
351 (display-backtrace internal-error-stack
352 (current-output-port)))))))))
355 (define (server-died)
356 (get-accumulated-output)
357 (close-port gds-port)
359 (run-hook gds-server-died-hook)
360 (throw 'server-died))
362 (define internal-error-stack #f)
364 (define gds-server-died-hook (make-hook))
366 (define (handle-instruction-1 state ins)
367 ;; Read the newline that always follows an instruction.
369 ;; Handle instruction from the UI frontend, and return updated state.
372 (write-form (cons 'modules (map module-name (loaded-modules))))
375 (let ((name (cadr ins)))
376 (write-form `(module ,name
377 ,(or (loaded-module-source name) "(no source file)")
378 ,@(sort (module-map (lambda (key value)
379 (symbol->string key))
380 (resolve-module-from-root name))
384 (or state (error "Not currently in debugger!"))
385 (write-status 'running)
386 (let ((name (cadr ins))
388 (let ((proc (module-ref the-ice-9-debugger-commands-module name)))
390 (apply proc state args)
391 (throw 'internal-error proc name args))))
394 (set-breakpoint! (case (cadddr ins)
395 ((debug-here) debug-here)
396 ((trace-here) trace-here)
397 ((trace-subtree) trace-subtree)
400 (display "Don't know `")
401 (display (cadddr ins))
402 (display "' behaviour; doing `debug-here' instead.\n")
404 (module-ref (resolve-module-from-root (cadr ins)) (caddr ins)))
407 (apply (lambda (correlator module port-name line column bpinfo code)
408 (with-input-from-string code
410 (set-port-filename! (current-input-port) port-name)
411 (set-port-line! (current-input-port) line)
412 (set-port-column! (current-input-port) column)
413 (let ((m (and module (resolve-module-from-root module))))
414 (let loop ((exprs '()) (x (read)))
416 ;; Expressions to be evaluated have all been
417 ;; read. Now hand them off to an
418 ;; eval-thread for the actual evaluation.
419 (with-mutex eval-work-mutex
420 (trc 'protocol-thread "evaluation work available")
421 (set! eval-work (cons* correlator m (reverse! exprs)))
422 (set! eval-work-available #t)
423 (broadcast-condition-variable eval-work-changed)
424 (wait-condition-variable eval-work-taken
426 (assert (not eval-work-available))
427 (trc 'protocol-thread "evaluation work underway"))
428 ;; Another complete expression read. Set
429 ;; breakpoints in the read code as specified
430 ;; by bpinfo, and add it to the list.
432 (install-breakpoints x bpinfo)
433 (loop (cons x exprs) (read)))))))))
437 (let ((matches (apropos-internal
438 (string-append "^" (regexp-quote (cadr ins))))))
439 (cond ((null? matches)
440 (write-form '(completion-result nil)))
442 ;;(write matches (current-error-port))
443 ;;(newline (current-error-port))
445 (let loop ((match (symbol->string (car matches)))
446 (matches (cdr matches)))
447 ;;(write match (current-error-port))
448 ;;(newline (current-error-port))
449 ;;(write matches (current-error-port))
450 ;;(newline (current-error-port))
453 (if (string-prefix=? match
454 (symbol->string (car matches)))
455 (loop match (cdr matches))
456 (loop (substring match 0
457 (- (string-length match) 1))
459 (if (string=? match (cadr ins))
460 (write-form `(completion-result
461 ,(map symbol->string matches)))
462 (write-form `(completion-result
466 (let ((thread (car (delq ui-read-thread (all-threads)))))
467 (write (cons 'target-thread thread))
469 (write (cons 'ui-read-thread ui-read-thread))
471 (system-async-mark (lambda ()
472 (debug-stack (make-stack #t 3) #:continuable))
476 (let ((thread (hash-ref eval-thread-table (cadr ins))))
477 (system-async-mark (lambda ()
478 (debug-stack (make-stack #t 3) #:continuable))
483 (define the-ice-9-debugger-commands-module
484 (resolve-module '(ice-9 debugger commands)))
486 (define (resolve-module-from-root name)
487 (save-module-excursion
489 (set-current-module the-root-module)
490 (resolve-module name))))
493 ;;;; {Module Browsing}
495 (define (loaded-module-source module-name)
496 ;; Return the file name that (ice-9 boot-9) probably loaded the
497 ;; named module from. (The `probably' is because `%load-path' might
498 ;; have changed since the module was loaded.)
499 (let* ((reverse-name (reverse module-name))
500 (name (symbol->string (car reverse-name)))
501 (dir-hint-module-name (reverse (cdr reverse-name)))
502 (dir-hint (apply string-append
504 (string-append (symbol->string elt) "/"))
505 dir-hint-module-name))))
506 (%search-load-path (in-vicinity dir-hint name))))
508 (define (loaded-modules)
509 ;; Return list of all loaded modules sorted by name.
510 (sort (apropos-fold-all (lambda (module acc) (cons module acc)) '())
512 (symlist<? (module-name m1) (module-name m2)))))
514 (define (symlist<? l1 l2)
515 ;; Return #t if symbol list L1 is alphabetically less than L2.
516 (cond ((null? l1) #t)
518 ((eq? (car l1) (car l2)) (symlist<? (cdr l1) (cdr l2)))
519 (else (string<? (symbol->string (car l1)) (symbol->string (car l2))))))
522 ;;;; {Source Breakpoint Installation}
524 (define (install-breakpoints x bpinfo)
525 (define (install-recursive x)
528 ;; Check source properties of x itself.
529 (let* ((infokey (cons (source-property x 'line)
530 (source-property x 'column)))
531 (bpentry (assoc infokey bpinfo)))
533 (let ((bp (set-breakpoint! debug-here x x)))
534 ;; FIXME: Here should transfer properties from the
535 ;; old breakpoint with index (cdr bpentry) to the
536 ;; new breakpoint. (Or else provide an alternative
537 ;; to set-breakpoint! that reuses the same
539 (write-form (list 'breakpoint-set
540 (source-property x 'filename)
544 ;; Check each of x's elements.
545 (for-each install-recursive x))))
546 (install-recursive x))
551 ;; Evaluation threads are unleashed by two possible triggers. One is
552 ;; a boolean variable, specific to each thread, that tells the thread
553 ;; to exit when set to #t. The other is another boolean variable, but
554 ;; global, indicating that there is an evaluation to perform:
555 (define eval-work-available #f)
557 ;; This variable, which is only valid when `eval-work-available' is
558 ;; #t, holds the evaluation to perform:
559 (define eval-work #f)
561 ;; A mutex protects against concurrent access to these variables.
562 (define eval-work-mutex (make-mutex))
564 ;; Changes in these variables are signaled by broadcasting the
565 ;; following condition variable.
566 (define eval-work-changed (make-condition-variable))
568 ;; When an evaluation thread takes some work, it tells the main GDS
569 ;; thread by signaling this condition variable.
570 (define eval-work-taken (make-condition-variable))
572 (define-macro (without-mutex m . body)
574 (lambda () (unlock-mutex ,m))
575 (lambda () (begin ,@body))
576 (lambda () (lock-mutex ,m))))
578 (define next-thread-number
581 (set! count (+ count 1))
584 (define eval-thread-table (make-hash-table 3))
586 (define (eval-thread depth thread-should-exit-thunk)
587 ;; Acquire mutex to check trigger variables.
588 (with-mutex eval-work-mutex
589 (let ((thread-number (next-thread-number)))
590 ;; Add this thread to global hash, so we can correlate back to
591 ;; this thread from the ID used by the GDS front end.
592 (hash-set! eval-thread-table thread-number (current-thread))
593 (trc 'eval-thread depth thread-number "entering loop")
595 ;; Tell the front end this thread is ready.
596 (write-form `(thread-status eval ,thread-number ready))
597 (cond ((thread-should-exit-thunk)
598 ;; Allow thread to exit.
602 ;; Take a local copy of the work, reset global
603 ;; variables, then do the work with mutex released.
604 (trc 'eval-thread depth thread-number "starting work")
605 (let* ((work eval-work)
606 (subthread-needed? #t)
607 (correlator (car work)))
608 ;; Tell the front end this thread is busy.
609 (write-form `(thread-status eval ,thread-number busy ,correlator))
610 (set! eval-work-available #f)
611 (signal-condition-variable eval-work-taken)
612 (without-mutex eval-work-mutex
613 ;; Before starting evaluation, create another eval
614 ;; thread like this one, so that it can take over
615 ;; if another evaluation is requested before this
617 (make-thread eval-thread (+ depth 1)
618 (lambda () (not subthread-needed?)))
619 ;; Do the evaluation(s).
620 (let loop2 ((m (cadr work))
624 (write-form `(eval-results ,correlator ,@results))
627 (append results (gds-eval (car exprs) m))))))
628 (trc 'eval-thread depth thread-number "work done")
629 ;; Tell the subthread that it should now exit.
630 (set! subthread-needed? #f)
631 (broadcast-condition-variable eval-work-changed)
632 ;; Loop for more work for this thread.
636 ;; Wait for something to change, then loop to check
637 ;; trigger variables again.
638 (trc 'eval-thread depth thread-number "wait")
639 (wait-condition-variable eval-work-changed eval-work-mutex)
640 (trc 'eval-thread depth thread-number "wait done")
642 (trc 'eval-thread depth thread-number "exiting")
643 ;; Tell the front end this thread is ready.
644 (write-form `(thread-status eval ,thread-number exiting)))))
646 (define (gds-eval x m)
647 ;; Consumer to accept possibly multiple values and present them for
648 ;; Emacs as a list of strings.
649 (define (value-consumer . values)
650 (if (unspecified? (car values))
653 (with-output-to-string (lambda () (write value))))
655 ;; Now do evaluation.
657 (let* ((do-eval (if m
659 (display "Evaluating in module ")
660 (write (module-name m))
663 (call-with-values (lambda ()
664 (start-stack 'gds-eval-stack
668 (display "Evaluating in current module ")
669 (write (module-name (current-module)))
672 (call-with-values (lambda ()
673 (start-stack 'gds-eval-stack
677 (with-output-to-string
683 ((misc-error signal unbound-variable
685 (apply display-error #f
686 (current-output-port) args)
687 (set! value '("error-in-evaluation")))
689 (display "EXCEPTION: ")
695 '("unhandled-exception-in-evaluation"))))))))))
696 (list output value))))
699 ;;; (emacs gds-client) ends here.