rtl-program-code -> program-code
[bpt/guile.git] / module / system / vm / traps.scm
1 ;;; Traps: stepping, breakpoints, and such.
2
3 ;; Copyright (C) 2010, 2012, 2013 Free Software Foundation, Inc.
4
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.
9 ;;;
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.
14 ;;;
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
18
19 ;;; Commentary:
20 ;;;
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
24 ;;; instruction.
25 ;;;
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.
30 ;;;
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.
36 ;;;
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.
40 ;;;
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.
47 ;;;
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.
51 ;;;
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.
54 ;;;
55 ;;; Code:
56
57 (define-module (system vm traps)
58 #:use-module (system base pmatch)
59 #:use-module (system vm vm)
60 #:use-module (system vm debug)
61 #:use-module (system vm frame)
62 #:use-module (system vm program)
63 #:use-module (system vm instruction)
64 #:use-module (system xref)
65 #:use-module (rnrs bytevectors)
66 #:export (trap-at-procedure-call
67 trap-in-procedure
68 trap-instructions-in-procedure
69 trap-at-procedure-ip-in-range
70 trap-at-source-location
71 trap-frame-finish
72 trap-in-dynamic-extent
73 trap-calls-in-dynamic-extent
74 trap-instructions-in-dynamic-extent
75 trap-calls-to-procedure
76 trap-matching-instructions))
77
78 (define-syntax arg-check
79 (syntax-rules ()
80 ((_ arg predicate? message)
81 (if (not (predicate? arg))
82 (error "bad argument ~a: ~a" 'arg message)))
83 ((_ arg predicate?)
84 (if (not (predicate? arg))
85 (error "bad argument ~a: expected ~a" 'arg 'predicate?)))))
86
87 (define (new-disabled-trap vm enable disable)
88 (let ((enabled? #f))
89 (define-syntax disabled?
90 (identifier-syntax
91 (disabled? (not enabled?))
92 ((set! disabled? val) (set! enabled? (not val)))))
93
94 (define* (enable-trap #:optional frame)
95 (if enabled? (error "trap already enabled"))
96 (enable frame)
97 (set! enabled? #t)
98 disable-trap)
99
100 (define* (disable-trap #:optional frame)
101 (if disabled? (error "trap already disabled"))
102 (disable frame)
103 (set! disabled? #t)
104 enable-trap)
105
106 enable-trap))
107
108 (define (new-enabled-trap vm frame enable disable)
109 ((new-disabled-trap vm enable disable) frame))
110
111 ;; Returns an absolute IP.
112 (define (program-last-ip prog)
113 (let ((pdi (find-program-debug-info (program-code prog))))
114 (and pdi (program-debug-info-size pdi))))
115
116 (define (frame-matcher proc match-code?)
117 (if match-code?
118 (if (program? proc)
119 (let ((start (program-code proc))
120 (end (program-last-ip proc)))
121 (lambda (frame)
122 (let ((ip (frame-instruction-pointer frame)))
123 (and (<= start ip) (< ip end)))))
124 (lambda (frame) #f))
125 (lambda (frame)
126 (eq? (frame-procedure frame) proc))))
127
128 ;; A basic trap, fires when a procedure is called.
129 ;;
130 (define* (trap-at-procedure-call proc handler #:key (vm (the-vm))
131 (closure? #f)
132 (our-frame? (frame-matcher proc closure?)))
133 (arg-check proc procedure?)
134 (arg-check handler procedure?)
135 (let ()
136 (define (apply-hook frame)
137 (if (our-frame? frame)
138 (handler frame)))
139
140 (new-enabled-trap
141 vm #f
142 (lambda (frame)
143 (add-hook! (vm-apply-hook vm) apply-hook))
144 (lambda (frame)
145 (remove-hook! (vm-apply-hook vm) apply-hook)))))
146
147 ;; A more complicated trap, traps when control enters a procedure.
148 ;;
149 ;; Control can enter a procedure via:
150 ;; * A procedure call.
151 ;; * A return to a procedure's frame on the stack.
152 ;; * A continuation returning directly to an application of this
153 ;; procedure.
154 ;;
155 ;; Control can leave a procedure via:
156 ;; * A normal return from the procedure.
157 ;; * An application of another procedure.
158 ;; * An invocation of a continuation.
159 ;; * An abort.
160 ;;
161 (define* (trap-in-procedure proc enter-handler exit-handler
162 #:key current-frame (vm (the-vm))
163 (closure? #f)
164 (our-frame? (frame-matcher proc closure?)))
165 (arg-check proc procedure?)
166 (arg-check enter-handler procedure?)
167 (arg-check exit-handler procedure?)
168 (let ((in-proc? #f))
169 (define (enter-proc frame)
170 (if in-proc?
171 (warn "already in proc" frame)
172 (begin
173 (enter-handler frame)
174 (set! in-proc? #t))))
175
176 (define (exit-proc frame)
177 (if in-proc?
178 (begin
179 (exit-handler frame)
180 (set! in-proc? #f))
181 (warn "not in proc" frame)))
182
183 (define (apply-hook frame)
184 (if in-proc?
185 (exit-proc frame))
186 (if (our-frame? frame)
187 (enter-proc frame)))
188
189 (define (push-cont-hook frame)
190 (if in-proc?
191 (exit-proc frame)))
192
193 (define (pop-cont-hook frame . values)
194 (if in-proc?
195 (exit-proc frame))
196 (if (our-frame? (frame-previous frame))
197 (enter-proc (frame-previous frame))))
198
199 (define (abort-hook frame . values)
200 (if in-proc?
201 (exit-proc frame))
202 (if (our-frame? frame)
203 (enter-proc frame)))
204
205 (define (restore-hook frame)
206 (if in-proc?
207 (exit-proc frame))
208 (if (our-frame? frame)
209 (enter-proc frame)))
210
211 (new-enabled-trap
212 vm current-frame
213 (lambda (frame)
214 (add-hook! (vm-apply-hook vm) apply-hook)
215 (add-hook! (vm-push-continuation-hook vm) push-cont-hook)
216 (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
217 (add-hook! (vm-abort-continuation-hook vm) abort-hook)
218 (add-hook! (vm-restore-continuation-hook vm) restore-hook)
219 (if (and frame (our-frame? frame))
220 (enter-proc frame)))
221 (lambda (frame)
222 (if in-proc?
223 (exit-proc frame))
224 (remove-hook! (vm-apply-hook vm) apply-hook)
225 (remove-hook! (vm-push-continuation-hook vm) push-cont-hook)
226 (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
227 (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
228 (remove-hook! (vm-restore-continuation-hook vm) restore-hook)))))
229
230 ;; Building on trap-in-procedure, we have trap-instructions-in-procedure
231 ;;
232 (define* (trap-instructions-in-procedure proc next-handler exit-handler
233 #:key current-frame (vm (the-vm))
234 (closure? #f)
235 (our-frame?
236 (frame-matcher proc closure?)))
237 (arg-check proc procedure?)
238 (arg-check next-handler procedure?)
239 (arg-check exit-handler procedure?)
240 (let ()
241 (define (next-hook frame)
242 (if (our-frame? frame)
243 (next-handler frame)))
244
245 (define (enter frame)
246 (add-hook! (vm-next-hook vm) next-hook)
247 (if frame (next-hook frame)))
248
249 (define (exit frame)
250 (exit-handler frame)
251 (remove-hook! (vm-next-hook vm) next-hook))
252
253 (trap-in-procedure proc enter exit
254 #:current-frame current-frame #:vm vm
255 #:our-frame? our-frame?)))
256
257 (define (non-negative-integer? x)
258 (and (number? x) (integer? x) (exact? x) (not (negative? x))))
259
260 (define (positive-integer? x)
261 (and (number? x) (integer? x) (exact? x) (positive? x)))
262
263 (define (range? x)
264 (and (list? x)
265 (and-map (lambda (x)
266 (and (pair? x)
267 (non-negative-integer? (car x))
268 (non-negative-integer? (cdr x))))
269 x)))
270
271 (define (in-range? range i)
272 (or-map (lambda (bounds)
273 (and (<= (car bounds) i)
274 (< i (cdr bounds))))
275 range))
276
277 ;; Building on trap-instructions-in-procedure, we have
278 ;; trap-at-procedure-ip-in-range.
279 ;;
280 (define* (trap-at-procedure-ip-in-range proc range handler
281 #:key current-frame (vm (the-vm))
282 (closure? #f)
283 (our-frame?
284 (frame-matcher proc closure?)))
285 (arg-check proc procedure?)
286 (arg-check range range?)
287 (arg-check handler procedure?)
288 (let ((fp-stack '()))
289 (define (cull-frames! fp)
290 (let lp ((frames fp-stack))
291 (if (and (pair? frames) (< (car frames) fp))
292 (lp (cdr frames))
293 (set! fp-stack frames))))
294
295 (define (next-handler frame)
296 (let ((fp (frame-address frame))
297 (ip (frame-instruction-pointer frame)))
298 (cull-frames! fp)
299 (let ((now-in-range? (in-range? range ip))
300 (was-in-range? (and (pair? fp-stack) (= (car fp-stack) fp))))
301 (cond
302 (was-in-range?
303 (if (not now-in-range?)
304 (set! fp-stack (cdr fp-stack))))
305 (now-in-range?
306 (set! fp-stack (cons fp fp-stack))
307 (handler frame))))))
308
309 (define (exit-handler frame)
310 (if (and (pair? fp-stack)
311 (= (car fp-stack) (frame-address frame)))
312 (set! fp-stack (cdr fp-stack))))
313
314 (trap-instructions-in-procedure proc next-handler exit-handler
315 #:current-frame current-frame #:vm vm
316 #:our-frame? our-frame?)))
317
318 (define (program-sources-by-line proc file)
319 (cond
320 ((program? proc)
321 (let ((code (program-code proc)))
322 (let lp ((sources (program-sources proc))
323 (out '()))
324 (if (pair? sources)
325 (lp (cdr sources)
326 (pmatch (car sources)
327 ((,start-ip ,start-file ,start-line . ,start-col)
328 (if (equal? start-file file)
329 (acons start-line
330 (if (pair? (cdr sources))
331 (pmatch (cadr sources)
332 ((,end-ip . _)
333 (cons (+ start-ip code)
334 (+ end-ip code)))
335 (else (error "unexpected")))
336 (cons (+ start-ip code)
337 (program-last-ip proc)))
338 out)
339 out))
340 (else (error "unexpected"))))
341 (let ((alist '()))
342 (for-each
343 (lambda (pair)
344 (set! alist
345 (assv-set! alist (car pair)
346 (cons (cdr pair)
347 (or (assv-ref alist (car pair))
348 '())))))
349 out)
350 (sort! alist (lambda (x y) (< (car x) (car y))))
351 alist)))))
352 (else '())))
353
354 (define (source->ip-range proc file line)
355 (or (or-map (lambda (line-and-ranges)
356 (cond
357 ((= (car line-and-ranges) line)
358 (cdr line-and-ranges))
359 ((> (car line-and-ranges) line)
360 (warn "no instructions found at" file ":" line
361 "; using line" (car line-and-ranges) "instead")
362 (cdr line-and-ranges))
363 (else #f)))
364 (program-sources-by-line proc file))
365 (begin
366 (warn "no instructions found for" file ":" line)
367 '())))
368
369 (define (source-closures-or-procedures file line)
370 (let ((closures (source-closures file line)))
371 (if (pair? closures)
372 (values closures #t)
373 (values (source-procedures file line) #f))))
374
375 ;; Building on trap-on-instructions-in-procedure, we have
376 ;; trap-at-source-location. The parameter `user-line' is one-indexed, as
377 ;; a user counts lines, instead of zero-indexed, as Guile counts lines.
378 ;;
379 (define* (trap-at-source-location file user-line handler
380 #:key current-frame (vm (the-vm)))
381 (arg-check file string?)
382 (arg-check user-line positive-integer?)
383 (arg-check handler procedure?)
384 (let ((traps #f))
385 (call-with-values
386 (lambda () (source-closures-or-procedures file (1- user-line)))
387 (lambda (procs closures?)
388 (new-enabled-trap
389 vm current-frame
390 (lambda (frame)
391 (set! traps
392 (map
393 (lambda (proc)
394 (let ((range (source->ip-range proc file (1- user-line))))
395 (trap-at-procedure-ip-in-range proc range handler
396 #:current-frame current-frame
397 #:vm vm
398 #:closure? closures?)))
399 procs))
400 (if (null? traps)
401 (error "No procedures found at ~a:~a." file user-line)))
402 (lambda (frame)
403 (for-each (lambda (trap) (trap frame)) traps)
404 (set! traps #f)))))))
405
406 \f
407
408 ;; On a different tack, now we're going to build up a set of traps that
409 ;; do useful things during the dynamic extent of a procedure's
410 ;; application. First, a trap for when a frame returns.
411 ;;
412 (define* (trap-frame-finish frame return-handler abort-handler
413 #:key (vm (the-vm)))
414 (arg-check frame frame?)
415 (arg-check return-handler procedure?)
416 (arg-check abort-handler procedure?)
417 (let ((fp (frame-address frame)))
418 (define (pop-cont-hook frame . values)
419 (if (and fp (eq? (frame-address frame) fp))
420 (begin
421 (set! fp #f)
422 (apply return-handler frame values))))
423
424 (define (abort-hook frame . values)
425 (if (and fp (< (frame-address frame) fp))
426 (begin
427 (set! fp #f)
428 (apply abort-handler frame values))))
429
430 (new-enabled-trap
431 vm frame
432 (lambda (frame)
433 (if (not fp)
434 (error "return-or-abort traps may only be enabled once"))
435 (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
436 (add-hook! (vm-abort-continuation-hook vm) abort-hook)
437 (add-hook! (vm-restore-continuation-hook vm) abort-hook))
438 (lambda (frame)
439 (set! fp #f)
440 (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
441 (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
442 (remove-hook! (vm-restore-continuation-hook vm) abort-hook)))))
443
444 ;; A more traditional dynamic-wind trap. Perhaps this should not be
445 ;; based on the above trap-frame-finish?
446 ;;
447 (define* (trap-in-dynamic-extent proc enter-handler return-handler abort-handler
448 #:key current-frame (vm (the-vm))
449 (closure? #f)
450 (our-frame? (frame-matcher proc closure?)))
451 (arg-check proc procedure?)
452 (arg-check enter-handler procedure?)
453 (arg-check return-handler procedure?)
454 (arg-check abort-handler procedure?)
455 (let ((exit-trap #f))
456 (define (return-hook frame . values)
457 (exit-trap frame) ; disable the return/abort trap.
458 (set! exit-trap #f)
459 (return-handler frame))
460
461 (define (abort-hook frame . values)
462 (exit-trap frame) ; disable the return/abort trap.
463 (set! exit-trap #f)
464 (abort-handler frame))
465
466 (define (apply-hook frame)
467 (if (and (not exit-trap) (our-frame? frame))
468 (begin
469 (enter-handler frame)
470 (set! exit-trap
471 (trap-frame-finish frame return-hook abort-hook
472 #:vm vm)))))
473
474 (new-enabled-trap
475 vm current-frame
476 (lambda (frame)
477 (add-hook! (vm-apply-hook vm) apply-hook))
478 (lambda (frame)
479 (if exit-trap
480 (abort-hook frame))
481 (set! exit-trap #f)
482 (remove-hook! (vm-apply-hook vm) apply-hook)))))
483
484 ;; Trapping all procedure calls within a dynamic extent, recording the
485 ;; depth of the call stack relative to the original procedure.
486 ;;
487 (define* (trap-calls-in-dynamic-extent proc apply-handler return-handler
488 #:key current-frame (vm (the-vm))
489 (closure? #f)
490 (our-frame?
491 (frame-matcher proc closure?)))
492 (arg-check proc procedure?)
493 (arg-check apply-handler procedure?)
494 (arg-check return-handler procedure?)
495 (let ((*call-depth* 0))
496 (define (trace-push frame)
497 (set! *call-depth* (1+ *call-depth*)))
498
499 (define (trace-pop frame . values)
500 (apply return-handler frame *call-depth* values)
501 (set! *call-depth* (1- *call-depth*)))
502
503 (define (trace-apply frame)
504 (apply-handler frame *call-depth*))
505
506 ;; FIXME: recalc depth on abort
507
508 (define (enter frame)
509 (add-hook! (vm-push-continuation-hook vm) trace-push)
510 (add-hook! (vm-pop-continuation-hook vm) trace-pop)
511 (add-hook! (vm-apply-hook vm) trace-apply))
512
513 (define (leave frame)
514 (remove-hook! (vm-push-continuation-hook vm) trace-push)
515 (remove-hook! (vm-pop-continuation-hook vm) trace-pop)
516 (remove-hook! (vm-apply-hook vm) trace-apply))
517
518 (define (return frame)
519 (leave frame))
520
521 (define (abort frame)
522 (leave frame))
523
524 (trap-in-dynamic-extent proc enter return abort
525 #:current-frame current-frame #:vm vm
526 #:our-frame? our-frame?)))
527
528 ;; Trapping all retired intructions within a dynamic extent.
529 ;;
530 (define* (trap-instructions-in-dynamic-extent proc next-handler
531 #:key current-frame (vm (the-vm))
532 (closure? #f)
533 (our-frame?
534 (frame-matcher proc closure?)))
535 (arg-check proc procedure?)
536 (arg-check next-handler procedure?)
537 (let ()
538 (define (trace-next frame)
539 (next-handler frame))
540
541 (define (enter frame)
542 (add-hook! (vm-next-hook vm) trace-next))
543
544 (define (leave frame)
545 (remove-hook! (vm-next-hook vm) trace-next))
546
547 (define (return frame)
548 (leave frame))
549
550 (define (abort frame)
551 (leave frame))
552
553 (trap-in-dynamic-extent proc enter return abort
554 #:current-frame current-frame #:vm vm
555 #:our-frame? our-frame?)))
556
557 ;; Traps calls and returns for a given procedure, keeping track of the call depth.
558 ;;
559 (define* (trap-calls-to-procedure proc apply-handler return-handler
560 #:key (vm (the-vm)))
561 (arg-check proc procedure?)
562 (arg-check apply-handler procedure?)
563 (arg-check return-handler procedure?)
564 (let ((pending-finish-traps '())
565 (last-fp #f))
566 (define (apply-hook frame)
567 (let ((depth (length pending-finish-traps)))
568
569 (apply-handler frame depth)
570
571 (if (not (eq? (frame-address frame) last-fp))
572 (let ((finish-trap #f))
573 (define (frame-finished frame)
574 (finish-trap frame) ;; disables the trap.
575 (set! pending-finish-traps
576 (delq finish-trap pending-finish-traps))
577 (set! finish-trap #f))
578
579 (define (return-hook frame . values)
580 (frame-finished frame)
581 (apply return-handler frame depth values))
582
583 ;; FIXME: abort handler?
584 (define (abort-hook frame . values)
585 (frame-finished frame))
586
587 (set! finish-trap
588 (trap-frame-finish frame return-hook abort-hook #:vm vm))
589 (set! pending-finish-traps
590 (cons finish-trap pending-finish-traps))))))
591
592 ;; The basic idea is that we install one trap that fires for calls,
593 ;; but that each call installs its own finish trap. Those finish
594 ;; traps remove themselves as their frames finish or abort.
595 ;;
596 ;; However since to the outside world we present the interface of
597 ;; just being one trap, disabling this calls-to-procedure trap
598 ;; should take care of disabling all of the pending finish traps. We
599 ;; keep track of pending traps through the pending-finish-traps
600 ;; list.
601 ;;
602 ;; So since we know that the trap-at-procedure will be enabled, and
603 ;; thus returning a disable closure, we make sure to wrap that
604 ;; closure in something that will disable pending finish traps.
605 (define (with-pending-finish-disablers trap)
606 (define (with-pending-finish-enablers trap)
607 (lambda* (#:optional frame)
608 (with-pending-finish-disablers (trap frame))))
609
610 (lambda* (#:optional frame)
611 (for-each (lambda (disable) (disable frame))
612 pending-finish-traps)
613 (set! pending-finish-traps '())
614 (with-pending-finish-enablers (trap frame))))
615
616 (with-pending-finish-disablers
617 (trap-at-procedure-call proc apply-hook #:vm vm))))
618
619 ;; Trap when the source location changes.
620 ;;
621 (define* (trap-matching-instructions frame-pred handler
622 #:key (vm (the-vm)))
623 (arg-check frame-pred procedure?)
624 (arg-check handler procedure?)
625 (let ()
626 (define (next-hook frame)
627 (if (frame-pred frame)
628 (handler frame)))
629
630 (new-enabled-trap
631 vm #f
632 (lambda (frame)
633 (add-hook! (vm-next-hook vm) next-hook))
634 (lambda (frame)
635 (remove-hook! (vm-next-hook vm) next-hook)))))