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