1 ;;; Traps: stepping, breakpoints, and such.
3 ;; Copyright (C) 2010 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 ;;; Guile's debugging capabilities come from the hooks that its VM
22 ;;; provides. For example, there is a hook that is fired when a function
23 ;;; is called, and even a hook that gets fired at every retired
26 ;;; But as the firing of these hooks is interleaved with the program
27 ;;; execution, if we want to debug a program, we have to write an
28 ;;; imperative program that mutates the state of these hooks, and to
29 ;;; dispatch the hooks to a more semantic context.
31 ;;; For example if we have placed a breakpoint at foo.scm:38, and
32 ;;; determined that that location maps to the 18th instruction in
33 ;;; procedure `bar', then we will need per-instruction hooks within
34 ;;; `bar' -- but when running other procedures, we can have the
35 ;;; per-instruction hooks off.
37 ;;; Our approach is to define "traps". The behavior of a trap is
38 ;;; specified when the trap is created. After creation, traps expose a
39 ;;; limited, uniform interface: they are either on or off.
41 ;;; To take our foo.scm:38 example again, we can define a trap that
42 ;;; calls a function when control transfers to that source line --
43 ;;; trap-at-source-location below. Calling the trap-at-source-location
44 ;;; function adds to the VM hooks in such at way that it can do its job.
45 ;;; The result of calling the function is a "disable-hook" closure that,
46 ;;; when called, will turn off that trap.
48 ;;; The result of calling the "disable-hook" closure, in turn, is an
49 ;;; "enable-hook" closure, which when called turns the hook back on, and
50 ;;; returns a "disable-hook" closure.
52 ;;; It's a little confusing. The summary is, call these functions to add
53 ;;; a trap; and call their return value to disable the trap.
57 (define-module (system vm traps)
58 #:use-module (system base pmatch)
59 #:use-module (system vm vm)
60 #:use-module (system vm frame)
61 #:use-module (system vm program)
62 #:use-module (system vm objcode)
63 #:use-module (system vm instruction)
64 #:use-module (system xref)
65 #:use-module (rnrs bytevectors)
66 #:export (trap-at-procedure-call
68 trap-instructions-in-procedure
69 trap-at-procedure-ip-in-range
70 trap-at-source-location
72 trap-in-dynamic-extent
73 trap-calls-in-dynamic-extent
74 trap-instructions-in-dynamic-extent
75 trap-calls-to-procedure))
77 (define-syntax arg-check
79 ((_ arg predicate? message)
80 (if (not (predicate? arg))
81 (error "bad argument ~a: ~a" 'arg message)))
83 (if (not (predicate? arg))
84 (error "bad argument ~a: expected ~a" 'arg 'predicate?)))))
86 (define (new-disabled-trap vm enable disable)
88 (define-syntax disabled?
90 (disabled? (not enabled?))
91 ((set! disabled? val) (set! enabled? (not val)))))
93 (define* (enable-trap #:optional frame)
94 (if enabled? (error "trap already enabled"))
99 (define* (disable-trap #:optional frame)
100 (if disabled? (error "trap already disabled"))
107 (define (new-enabled-trap vm frame enable disable)
108 ((new-disabled-trap vm enable disable) frame))
110 (define (frame-matcher proc match-objcode?)
113 (let ((frame-proc (frame-procedure frame)))
114 (or (eq? frame-proc proc)
115 (and (program? frame-proc)
116 (eq? (program-objcode frame-proc)
117 (program-objcode proc))))))
119 (eq? (frame-procedure frame) proc))))
121 ;; A basic trap, fires when a procedure is called.
123 (define* (trap-at-procedure-call proc handler #:key (vm (the-vm))
125 (our-frame? (frame-matcher proc closure?)))
126 (arg-check proc procedure?)
127 (arg-check handler procedure?)
129 (define (apply-hook frame)
130 (if (our-frame? frame)
136 (add-hook! (vm-apply-hook vm) apply-hook))
138 (remove-hook! (vm-apply-hook vm) apply-hook)))))
140 ;; A more complicated trap, traps when control enters a procedure.
142 ;; Control can enter a procedure via:
143 ;; * A procedure call.
144 ;; * A return to a procedure's frame on the stack.
145 ;; * A continuation returning directly to an application of this
148 ;; Control can leave a procedure via:
149 ;; * A normal return from the procedure.
150 ;; * An application of another procedure.
151 ;; * An invocation of a continuation.
154 (define* (trap-in-procedure proc enter-handler exit-handler
155 #:key current-frame (vm (the-vm))
157 (our-frame? (frame-matcher proc closure?)))
158 (arg-check proc procedure?)
159 (arg-check enter-handler procedure?)
160 (arg-check exit-handler procedure?)
162 (define (enter-proc frame)
164 (warn "already in proc" frame)
166 (enter-handler frame)
167 (set! in-proc? #t))))
169 (define (exit-proc frame)
174 (warn "not in proc" frame)))
176 (define (apply-hook frame)
179 (if (our-frame? frame)
182 (define (push-cont-hook frame)
186 (define (pop-cont-hook frame)
189 (if (our-frame? (frame-previous frame))
190 (enter-proc (frame-previous frame))))
192 (define (abort-hook frame)
195 (if (our-frame? frame)
198 (define (restore-hook frame)
201 (if (our-frame? frame)
207 (add-hook! (vm-apply-hook vm) apply-hook)
208 (add-hook! (vm-push-continuation-hook vm) push-cont-hook)
209 (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
210 (add-hook! (vm-abort-continuation-hook vm) abort-hook)
211 (add-hook! (vm-restore-continuation-hook vm) restore-hook)
212 (if (and frame (our-frame? frame))
217 (remove-hook! (vm-apply-hook vm) apply-hook)
218 (remove-hook! (vm-push-continuation-hook vm) push-cont-hook)
219 (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
220 (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
221 (remove-hook! (vm-restore-continuation-hook vm) restore-hook)))))
223 ;; Building on trap-in-procedure, we have trap-instructions-in-procedure
225 (define* (trap-instructions-in-procedure proc next-handler exit-handler
226 #:key current-frame (vm (the-vm))
229 (frame-matcher proc closure?)))
230 (arg-check proc procedure?)
231 (arg-check next-handler procedure?)
232 (arg-check exit-handler procedure?)
234 (define (next-hook frame)
235 (if (our-frame? frame)
236 (next-handler frame)))
238 (define (enter frame)
239 (add-hook! (vm-next-hook vm) next-hook)
240 (if frame (next-hook frame)))
244 (remove-hook! (vm-next-hook vm) next-hook))
246 (trap-in-procedure proc enter exit
247 #:current-frame current-frame #:vm vm
248 #:our-frame? our-frame?)))
250 (define (non-negative-integer? x)
251 (and (number? x) (integer? x) (exact? x) (not (negative? x))))
253 (define (positive-integer? x)
254 (and (number? x) (integer? x) (exact? x) (positive? x)))
260 (non-negative-integer? (car x))
261 (non-negative-integer? (cdr x))))
264 (define (in-range? range i)
265 (or-map (lambda (bounds)
266 (and (<= (car bounds) i)
270 ;; Building on trap-instructions-in-procedure, we have
271 ;; trap-instructions-in-procedure.
273 (define* (trap-at-procedure-ip-in-range proc range handler
274 #:key current-frame (vm (the-vm))
277 (frame-matcher proc closure?)))
278 (arg-check proc procedure?)
279 (arg-check range range?)
280 (arg-check handler procedure?)
281 (let ((fp-stack '()))
282 (define (cull-frames! fp)
283 (let lp ((frames fp-stack))
284 (if (and (pair? frames) (< (car frames) fp))
286 (set! fp-stack frames))))
288 (define (next-handler frame)
289 (let ((fp (frame-address frame))
290 (ip (frame-instruction-pointer frame)))
292 (let ((now-in-range? (in-range? range ip))
293 (was-in-range? (and (pair? fp-stack) (= (car fp-stack) fp))))
296 (if (not now-in-range?)
297 (set! fp-stack (cdr fp-stack))))
299 (set! fp-stack (cons fp fp-stack))
302 (define (exit-handler frame)
303 (if (and (pair? fp-stack)
304 (= (car fp-stack) (frame-address frame)))
305 (set! fp-stack (cdr fp-stack))))
307 (trap-instructions-in-procedure proc next-handler exit-handler
308 #:current-frame current-frame #:vm vm
309 #:our-frame? our-frame?)))
311 ;; FIXME: pull this definition from elsewhere.
312 (define *bytecode-header-len* 8)
314 ;; FIXME: define this in objcode somehow. We are reffing the first
315 ;; uint32 in the objcode, which is the length of the program (without
317 (define (program-last-ip prog)
318 (bytevector-u32-native-ref (objcode->bytecode (program-objcode prog)) 0))
320 ;; We could decompile the program to get this, but that seems like a
322 (define (bytecode-instruction-length bytecode ip)
323 (let* ((idx (+ ip *bytecode-header-len*))
324 (inst (opcode->instruction (bytevector-u8-ref bytecode idx))))
325 ;; 1+ for the instruction itself.
327 ((eq? inst 'load-program)
328 (+ (bytevector-u32-native-ref bytecode (+ idx 1))
329 (bytevector-u32-native-ref bytecode (+ idx 5))))
330 ((< (instruction-length inst) 0)
331 ;; variable length instruction -- the length is encoded in the
332 ;; instruction stream.
333 (+ (ash (bytevector-u8-ref bytecode (+ idx 1)) 16)
334 (ash (bytevector-u8-ref bytecode (+ idx 2)) 8)
335 (bytevector-u8-ref bytecode (+ idx 3))))
338 (instruction-length inst))))))
340 ;; Source information could in theory be correlated with the ip of the
341 ;; instruction, or the ip just after the instruction is retired. Guile
342 ;; does the latter, to make backtraces easy -- an error produced while
343 ;; running an opcode always happens after it has retired its arguments.
345 ;; But for breakpoints and such, we need the ip before the instruction
346 ;; is retired -- before it has had a chance to do anything. So here we
347 ;; change from the post-retire addresses given by program-sources to
348 ;; pre-retire addresses.
350 (define (program-sources-before-retire proc)
351 (let ((bv (objcode->bytecode (program-objcode proc))))
352 (let lp ((in (program-sources proc))
360 ((,post-ip . ,source)
364 (lp2 next (+ next (bytecode-instruction-length bv next)))
366 (acons ip source out)
369 (error "unexpected"))))))))
371 (define (program-sources-by-line proc file)
372 (let lp ((sources (program-sources-before-retire proc))
376 (pmatch (car sources)
377 ((,start-ip ,start-file ,start-line . ,start-col)
378 (if (equal? start-file file)
379 (cons (cons start-line
380 (if (pair? (cdr sources))
381 (pmatch (cadr sources)
383 (cons start-ip end-ip))
384 (else (error "unexpected")))
385 (cons start-ip (program-last-ip proc))))
388 (else (error "unexpected"))))
393 (assv-set! alist (car pair)
395 (or (assv-ref alist (car pair))
398 (sort! alist (lambda (x y) (< (car x) (car y))))
401 (define (source->ip-range proc file line)
402 (or (or-map (lambda (line-and-ranges)
404 ((= (car line-and-ranges) line)
405 (cdr line-and-ranges))
406 ((> (car line-and-ranges) line)
407 (warn "no instructions found at" file ":" line
408 "; using line" (car line-and-ranges) "instead")
409 (cdr line-and-ranges))
411 (program-sources-by-line proc file))
413 (warn "no instructions found for" file ":" line)
416 (define (source-closures-or-procedures file line)
417 (let ((closures (source-closures file line)))
420 (values (source-procedures file line) #f))))
422 ;; Building on trap-on-instructions-in-procedure, we have
423 ;; trap-at-source-location. The parameter `user-line' is one-indexed, as
424 ;; a user counts lines, instead of zero-indexed, as Guile counts lines.
426 (define* (trap-at-source-location file user-line handler
427 #:key current-frame (vm (the-vm)))
428 (arg-check file string?)
429 (arg-check user-line positive-integer?)
430 (arg-check handler procedure?)
433 (lambda () (source-closures-or-procedures file (1- user-line)))
434 (lambda (procs closures?)
441 (let ((range (source->ip-range proc file (1- user-line))))
442 (trap-at-procedure-ip-in-range proc range handler
443 #:current-frame current-frame
445 #:closure? closures?)))
448 (error "No procedures found at ~a:~a." file user-line)))
450 (for-each (lambda (trap) (trap frame)) traps)
451 (set! traps #f)))))))
455 ;; On a different tack, now we're going to build up a set of traps that
456 ;; do useful things during the dynamic extent of a procedure's
457 ;; application. First, a trap for when a frame returns.
459 (define* (trap-frame-finish frame return-handler abort-handler
461 (arg-check frame frame?)
462 (arg-check return-handler procedure?)
463 (arg-check abort-handler procedure?)
464 (let ((fp (frame-address frame)))
465 (define (pop-cont-hook frame)
466 (if (and fp (eq? (frame-address frame) fp))
469 (return-handler frame))))
471 (define (abort-hook frame)
472 (if (and fp (< (frame-address frame) fp))
475 (abort-handler frame))))
481 (error "return-or-abort traps may only be enabled once"))
482 (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
483 (add-hook! (vm-abort-continuation-hook vm) abort-hook)
484 (add-hook! (vm-restore-continuation-hook vm) abort-hook))
487 (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
488 (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
489 (remove-hook! (vm-restore-continuation-hook vm) abort-hook)))))
491 ;; A more traditional dynamic-wind trap. Perhaps this should not be
492 ;; based on the above trap-frame-finish?
494 (define* (trap-in-dynamic-extent proc enter-handler return-handler abort-handler
495 #:key current-frame (vm (the-vm))
497 (our-frame? (frame-matcher proc closure?)))
498 (arg-check proc procedure?)
499 (arg-check enter-handler procedure?)
500 (arg-check return-handler procedure?)
501 (arg-check abort-handler procedure?)
502 (let ((exit-trap #f))
503 (define (return-hook frame)
504 (exit-trap frame) ; disable the return/abort trap.
506 (return-handler frame))
508 (define (abort-hook frame)
509 (exit-trap frame) ; disable the return/abort trap.
511 (abort-handler frame))
513 (define (apply-hook frame)
514 (if (and (not exit-trap) (our-frame? frame))
516 (enter-handler frame)
518 (trap-frame-finish frame return-hook abort-hook
524 (add-hook! (vm-apply-hook vm) apply-hook))
529 (remove-hook! (vm-apply-hook vm) apply-hook)))))
531 ;; Trapping all procedure calls within a dynamic extent, recording the
532 ;; depth of the call stack relative to the original procedure.
534 (define* (trap-calls-in-dynamic-extent proc apply-handler return-handler
535 #:key current-frame (vm (the-vm))
538 (frame-matcher proc closure?)))
539 (arg-check proc procedure?)
540 (arg-check apply-handler procedure?)
541 (arg-check return-handler procedure?)
542 (let ((*call-depth* 0))
543 (define (trace-push frame)
544 (set! *call-depth* (1+ *call-depth*)))
546 (define (trace-pop frame)
547 (return-handler frame *call-depth*)
548 (set! *call-depth* (1- *call-depth*)))
550 (define (trace-apply frame)
551 (apply-handler frame *call-depth*))
553 ;; FIXME: recalc depth on abort
555 (define (enter frame)
556 (add-hook! (vm-push-continuation-hook vm) trace-push)
557 (add-hook! (vm-pop-continuation-hook vm) trace-pop)
558 (add-hook! (vm-apply-hook vm) trace-apply))
560 (define (leave frame)
561 (remove-hook! (vm-push-continuation-hook vm) trace-push)
562 (remove-hook! (vm-pop-continuation-hook vm) trace-pop)
563 (remove-hook! (vm-apply-hook vm) trace-apply))
565 (define (return frame)
568 (define (abort frame)
571 (trap-in-dynamic-extent proc enter return abort
572 #:current-frame current-frame #:vm vm
573 #:our-frame? our-frame?)))
575 ;; Trapping all retired intructions within a dynamic extent.
577 (define* (trap-instructions-in-dynamic-extent proc next-handler
578 #:key current-frame (vm (the-vm))
581 (frame-matcher proc closure?)))
582 (arg-check proc procedure?)
583 (arg-check next-handler procedure?)
585 (define (trace-next frame)
586 (next-handler frame))
588 (define (enter frame)
589 (add-hook! (vm-next-hook vm) trace-next))
591 (define (leave frame)
592 (remove-hook! (vm-next-hook vm) trace-next))
594 (define (return frame)
597 (define (abort frame)
600 (trap-in-dynamic-extent proc enter return abort
601 #:current-frame current-frame #:vm vm
602 #:our-frame? our-frame?)))
604 ;; Traps calls and returns for a given procedure, keeping track of the call depth.
606 (define* (trap-calls-to-procedure proc apply-handler return-handler
607 #:key (width 80) (vm (the-vm)))
608 (arg-check proc procedure?)
609 (arg-check apply-handler procedure?)
610 (arg-check return-handler procedure?)
611 (let ((pending-finish-traps '())
613 (define (apply-hook frame)
614 (let ((depth (length pending-finish-traps)))
616 (apply-handler frame depth)
618 (if (not (eq? (frame-address frame) last-fp))
619 (let ((finish-trap #f))
620 (define (frame-finished frame)
621 (finish-trap frame) ;; disables the trap.
622 (set! pending-finish-traps
623 (delq finish-trap pending-finish-traps))
624 (set! finish-trap #f))
626 (define (return-hook frame)
627 (frame-finished frame)
628 (return-handler frame depth))
630 ;; FIXME: abort handler?
631 (define (abort-hook frame)
632 (frame-finished frame))
635 (trap-frame-finish frame return-hook abort-hook #:vm vm))
636 (set! pending-finish-traps
637 (cons finish-trap pending-finish-traps))))))
639 ;; The basic idea is that we install one trap that fires for calls,
640 ;; but that each call installs its own finish trap. Those finish
641 ;; traps remove themselves as their frames finish or abort.
643 ;; However since to the outside world we present the interface of
644 ;; just being one trap, disabling this calls-to-procedure trap
645 ;; should take care of disabling all of the pending finish traps. We
646 ;; keep track of pending traps through the pending-finish-traps
649 ;; So since we know that the trap-at-procedure will be enabled, and
650 ;; thus returning a disable closure, we make sure to wrap that
651 ;; closure in something that will disable pending finish traps.
652 (define (with-pending-finish-disablers trap)
653 (define (with-pending-finish-enablers trap)
654 (lambda* (#:optional frame)
655 (with-pending-finish-disablers (trap frame))))
657 (lambda* (#:optional frame)
658 (for-each (lambda (disable) (disable frame))
659 pending-finish-traps)
660 (set! pending-finish-traps '())
661 (with-pending-finish-enablers (trap frame))))
663 (with-pending-finish-disablers
664 (trap-at-procedure-call proc apply-hook #:vm vm))))