traps.scm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. ;;; Traps: stepping, breakpoints, and such.
  2. ;; Copyright (C) 2010,2012-2014,2017-2018 Free Software Foundation, Inc.
  3. ;;; This library is free software; you can redistribute it and/or
  4. ;;; modify it under the terms of the GNU Lesser General Public
  5. ;;; License as published by the Free Software Foundation; either
  6. ;;; version 3 of the License, or (at your option) any later version.
  7. ;;;
  8. ;;; This library is distributed in the hope that it will be useful,
  9. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;;; Lesser General Public License for more details.
  12. ;;;
  13. ;;; You should have received a copy of the GNU Lesser General Public
  14. ;;; License along with this library; if not, write to the Free Software
  15. ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  16. ;;; Commentary:
  17. ;;;
  18. ;;; Guile's debugging capabilities come from the hooks that its VM
  19. ;;; provides. For example, there is a hook that is fired when a function
  20. ;;; is called, and even a hook that gets fired at every retired
  21. ;;; instruction.
  22. ;;;
  23. ;;; But as the firing of these hooks is interleaved with the program
  24. ;;; execution, if we want to debug a program, we have to write an
  25. ;;; imperative program that mutates the state of these hooks, and to
  26. ;;; dispatch the hooks to a more semantic context.
  27. ;;;
  28. ;;; For example if we have placed a breakpoint at foo.scm:38, and
  29. ;;; determined that that location maps to the 18th instruction in
  30. ;;; procedure `bar', then we will need per-instruction hooks within
  31. ;;; `bar' -- but when running other procedures, we can have the
  32. ;;; per-instruction hooks off.
  33. ;;;
  34. ;;; Our approach is to define "traps". The behavior of a trap is
  35. ;;; specified when the trap is created. After creation, traps expose a
  36. ;;; limited, uniform interface: they are either on or off.
  37. ;;;
  38. ;;; To take our foo.scm:38 example again, we can define a trap that
  39. ;;; calls a function when control transfers to that source line --
  40. ;;; trap-at-source-location below. Calling the trap-at-source-location
  41. ;;; function adds to the VM hooks in such at way that it can do its job.
  42. ;;; The result of calling the function is a "disable-hook" closure that,
  43. ;;; when called, will turn off that trap.
  44. ;;;
  45. ;;; The result of calling the "disable-hook" closure, in turn, is an
  46. ;;; "enable-hook" closure, which when called turns the hook back on, and
  47. ;;; returns a "disable-hook" closure.
  48. ;;;
  49. ;;; It's a little confusing. The summary is, call these functions to add
  50. ;;; a trap; and call their return value to disable the trap.
  51. ;;;
  52. ;;; Code:
  53. (define-module (system vm traps)
  54. #:use-module (ice-9 match)
  55. #:use-module (system vm vm)
  56. #:use-module (system vm debug)
  57. #:use-module (system vm frame)
  58. #:use-module (system vm program)
  59. #:use-module (system xref)
  60. #:use-module (rnrs bytevectors)
  61. #:export (trap-at-procedure-call
  62. trap-in-procedure
  63. trap-instructions-in-procedure
  64. trap-at-procedure-ip-in-range
  65. trap-at-source-location
  66. trap-frame-finish
  67. trap-in-dynamic-extent
  68. trap-calls-in-dynamic-extent
  69. trap-instructions-in-dynamic-extent
  70. trap-calls-to-procedure
  71. trap-matching-instructions))
  72. (define-syntax arg-check
  73. (syntax-rules ()
  74. ((_ arg predicate? message)
  75. (if (not (predicate? arg))
  76. (error "bad argument ~a: ~a" 'arg message)))
  77. ((_ arg predicate?)
  78. (if (not (predicate? arg))
  79. (error "bad argument ~a: expected ~a" 'arg 'predicate?)))))
  80. (define (new-disabled-trap enable disable)
  81. (let ((enabled? #f))
  82. (define-syntax disabled?
  83. (identifier-syntax
  84. (disabled? (not enabled?))
  85. ((set! disabled? val) (set! enabled? (not val)))))
  86. (define* (enable-trap #:optional frame)
  87. (if enabled? (error "trap already enabled"))
  88. (enable frame)
  89. (set! enabled? #t)
  90. disable-trap)
  91. (define* (disable-trap #:optional frame)
  92. (if disabled? (error "trap already disabled"))
  93. (disable frame)
  94. (set! disabled? #t)
  95. enable-trap)
  96. enable-trap))
  97. (define (new-enabled-trap frame enable disable)
  98. ((new-disabled-trap enable disable) frame))
  99. ;; Returns an absolute IP.
  100. (define (program-last-ip prog)
  101. (let ((pdi (find-program-debug-info (program-code prog))))
  102. (and pdi
  103. (+ (program-debug-info-addr pdi)
  104. (program-debug-info-size pdi)))))
  105. (define (frame-matcher proc)
  106. (let ((proc (if (struct? proc)
  107. (procedure proc)
  108. proc)))
  109. (cond
  110. ((program? proc)
  111. (let ((start (program-code proc))
  112. (end (program-last-ip proc)))
  113. (lambda (frame)
  114. (let ((ip (frame-instruction-pointer frame)))
  115. (and (<= start ip)
  116. end (< ip end))))))
  117. ((struct? proc)
  118. (frame-matcher (procedure proc)))
  119. (else
  120. (error "Not a VM program" proc)))))
  121. ;; A basic trap, fires when a procedure is called.
  122. ;;
  123. (define* (trap-at-procedure-call proc handler #:key
  124. (our-frame? (frame-matcher proc)))
  125. (arg-check proc procedure?)
  126. (arg-check handler procedure?)
  127. (let ()
  128. (define (apply-hook frame)
  129. (if (our-frame? frame)
  130. (handler frame)))
  131. (new-enabled-trap
  132. #f
  133. (lambda (frame)
  134. (vm-add-apply-hook! apply-hook))
  135. (lambda (frame)
  136. (vm-remove-apply-hook! apply-hook)))))
  137. ;; A more complicated trap, traps when control enters a procedure.
  138. ;;
  139. ;; Control can enter a procedure via:
  140. ;; * A procedure call.
  141. ;; * A return to a procedure's frame on the stack.
  142. ;; * A continuation returning directly to an application of this
  143. ;; procedure.
  144. ;;
  145. ;; Control can leave a procedure via:
  146. ;; * A normal return from the procedure.
  147. ;; * An application of another procedure.
  148. ;; * An invocation of a continuation.
  149. ;; * An abort.
  150. ;;
  151. (define* (trap-in-procedure proc enter-handler exit-handler
  152. #:key current-frame
  153. (our-frame? (frame-matcher proc)))
  154. (arg-check proc procedure?)
  155. (arg-check enter-handler procedure?)
  156. (arg-check exit-handler procedure?)
  157. (let ((in-proc? #f))
  158. (define (enter-proc frame)
  159. (if in-proc?
  160. (warn "already in proc" frame)
  161. (begin
  162. (enter-handler frame)
  163. (set! in-proc? #t))))
  164. (define (exit-proc frame)
  165. (if in-proc?
  166. (begin
  167. (exit-handler frame)
  168. (set! in-proc? #f))
  169. (warn "not in proc" frame)))
  170. (define (apply-hook frame)
  171. (if in-proc?
  172. (exit-proc frame))
  173. (if (our-frame? frame)
  174. (enter-proc frame)))
  175. (define (return-hook frame)
  176. (if in-proc?
  177. (exit-proc frame))
  178. (let ((prev (frame-previous frame)))
  179. (if (our-frame? prev)
  180. (enter-proc prev))))
  181. (define (abort-hook frame)
  182. (if in-proc?
  183. (exit-proc frame))
  184. (if (our-frame? frame)
  185. (enter-proc frame)))
  186. (new-enabled-trap
  187. current-frame
  188. (lambda (frame)
  189. (vm-add-apply-hook! apply-hook)
  190. (vm-add-return-hook! return-hook)
  191. (vm-add-abort-hook! abort-hook)
  192. (if (and frame (our-frame? frame))
  193. (enter-proc frame)))
  194. (lambda (frame)
  195. (if in-proc?
  196. (exit-proc frame))
  197. (vm-remove-apply-hook! apply-hook)
  198. (vm-remove-return-hook! return-hook)
  199. (vm-remove-abort-hook! abort-hook)))))
  200. ;; Building on trap-in-procedure, we have trap-instructions-in-procedure
  201. ;;
  202. (define* (trap-instructions-in-procedure proc next-handler exit-handler
  203. #:key current-frame
  204. (our-frame? (frame-matcher proc)))
  205. (arg-check proc procedure?)
  206. (arg-check next-handler procedure?)
  207. (arg-check exit-handler procedure?)
  208. (let ()
  209. (define (next-hook frame)
  210. (if (our-frame? frame)
  211. (next-handler frame)))
  212. (define (enter frame)
  213. (vm-add-next-hook! next-hook)
  214. (if frame (next-hook frame)))
  215. (define (exit frame)
  216. (exit-handler frame)
  217. (vm-remove-next-hook! next-hook))
  218. (trap-in-procedure proc enter exit
  219. #:current-frame current-frame
  220. #:our-frame? our-frame?)))
  221. (define (non-negative-integer? x)
  222. (and (number? x) (integer? x) (exact? x) (not (negative? x))))
  223. (define (positive-integer? x)
  224. (and (number? x) (integer? x) (exact? x) (positive? x)))
  225. (define (range? x)
  226. (and (list? x)
  227. (and-map (lambda (x)
  228. (and (pair? x)
  229. (non-negative-integer? (car x))
  230. (non-negative-integer? (cdr x))))
  231. x)))
  232. (define (in-range? range i)
  233. (or-map (lambda (bounds)
  234. (and (<= (car bounds) i)
  235. (< i (cdr bounds))))
  236. range))
  237. ;; Building on trap-instructions-in-procedure, we have
  238. ;; trap-at-procedure-ip-in-range.
  239. ;;
  240. (define* (trap-at-procedure-ip-in-range proc range handler
  241. #:key current-frame
  242. (our-frame? (frame-matcher proc)))
  243. (arg-check proc procedure?)
  244. (arg-check range range?)
  245. (arg-check handler procedure?)
  246. (let ((fp-stack '()))
  247. (define (cull-frames! fp)
  248. (let lp ((frames fp-stack))
  249. (if (and (pair? frames) (< (car frames) fp))
  250. (lp (cdr frames))
  251. (set! fp-stack frames))))
  252. (define (next-handler frame)
  253. (let ((fp (frame-address frame))
  254. (ip (frame-instruction-pointer frame)))
  255. (cull-frames! fp)
  256. (let ((now-in-range? (in-range? range ip))
  257. (was-in-range? (and (pair? fp-stack) (= (car fp-stack) fp))))
  258. (cond
  259. (was-in-range?
  260. (if (not now-in-range?)
  261. (set! fp-stack (cdr fp-stack))))
  262. (now-in-range?
  263. (set! fp-stack (cons fp fp-stack))
  264. (handler frame))))))
  265. (define (exit-handler frame)
  266. (if (and (pair? fp-stack)
  267. (= (car fp-stack) (frame-address frame)))
  268. (set! fp-stack (cdr fp-stack))))
  269. (trap-instructions-in-procedure proc next-handler exit-handler
  270. #:current-frame current-frame
  271. #:our-frame? our-frame?)))
  272. (define (program-sources-by-line proc file)
  273. (cond
  274. ((program? proc)
  275. (let ((code (program-code proc)))
  276. (let lp ((sources (program-sources proc))
  277. (out '()))
  278. (match sources
  279. (((start-ip start-file start-line . start-col) . sources)
  280. (lp sources
  281. (if (equal? start-file file)
  282. (acons start-line
  283. (cons (+ start-ip code)
  284. (match sources
  285. (((end-ip . _) . _)
  286. (+ end-ip code))
  287. (()
  288. (program-last-ip proc))))
  289. out)
  290. out)))
  291. (()
  292. (let ((alist '()))
  293. (for-each
  294. (lambda (pair)
  295. (set! alist
  296. (assv-set! alist (car pair)
  297. (cons (cdr pair)
  298. (or (assv-ref alist (car pair))
  299. '())))))
  300. out)
  301. (sort! alist (lambda (x y) (< (car x) (car y))))
  302. alist))))))
  303. (else '())))
  304. (define (source->ip-range proc file line)
  305. (or (or-map (lambda (line-and-ranges)
  306. (cond
  307. ((= (car line-and-ranges) line)
  308. (cdr line-and-ranges))
  309. ((> (car line-and-ranges) line)
  310. (warn "no instructions found at" file ":" line
  311. "; using line" (car line-and-ranges) "instead")
  312. (cdr line-and-ranges))
  313. (else #f)))
  314. (program-sources-by-line proc file))
  315. (begin
  316. (warn "no instructions found for" file ":" line)
  317. '())))
  318. (define (source-closures-or-procedures file line)
  319. (let ((closures (source-closures file line)))
  320. (if (pair? closures)
  321. (values closures #t)
  322. (values (source-procedures file line) #f))))
  323. ;; Building on trap-on-instructions-in-procedure, we have
  324. ;; trap-at-source-location. The parameter `user-line' is one-indexed, as
  325. ;; a user counts lines, instead of zero-indexed, as Guile counts lines.
  326. ;;
  327. (define* (trap-at-source-location file user-line handler #:key current-frame)
  328. (arg-check file string?)
  329. (arg-check user-line positive-integer?)
  330. (arg-check handler procedure?)
  331. (let ((traps #f))
  332. (call-with-values
  333. (lambda () (source-closures-or-procedures file (1- user-line)))
  334. (lambda (procs closures?)
  335. (new-enabled-trap
  336. current-frame
  337. (lambda (frame)
  338. (set! traps
  339. (map
  340. (lambda (proc)
  341. (let ((range (source->ip-range proc file (1- user-line))))
  342. (trap-at-procedure-ip-in-range proc range handler
  343. #:current-frame
  344. current-frame)))
  345. procs))
  346. (if (null? traps)
  347. (error "No procedures found at ~a:~a." file user-line)))
  348. (lambda (frame)
  349. (for-each (lambda (trap) (trap frame)) traps)
  350. (set! traps #f)))))))
  351. ;; On a different tack, now we're going to build up a set of traps that
  352. ;; do useful things during the dynamic extent of a procedure's
  353. ;; application. First, a trap for when a frame returns.
  354. ;;
  355. (define (trap-frame-finish frame return-handler abort-handler)
  356. (arg-check frame frame?)
  357. (arg-check return-handler procedure?)
  358. (arg-check abort-handler procedure?)
  359. (let ((fp (frame-address frame)))
  360. (define (return-hook frame)
  361. (if (and fp (<= (frame-address frame) fp))
  362. (begin
  363. (set! fp #f)
  364. (return-handler frame))))
  365. (define (abort-hook frame)
  366. (if (and fp (<= (frame-address frame) fp))
  367. (begin
  368. (set! fp #f)
  369. (abort-handler frame))))
  370. (new-enabled-trap
  371. frame
  372. (lambda (frame)
  373. (if (not fp)
  374. (error "return-or-abort traps may only be enabled once"))
  375. (vm-add-return-hook! return-hook)
  376. (vm-add-abort-hook! abort-hook))
  377. (lambda (frame)
  378. (set! fp #f)
  379. (vm-remove-return-hook! return-hook)
  380. (vm-remove-abort-hook! abort-hook)))))
  381. ;; A more traditional dynamic-wind trap. Perhaps this should not be
  382. ;; based on the above trap-frame-finish?
  383. ;;
  384. (define* (trap-in-dynamic-extent proc enter-handler return-handler abort-handler
  385. #:key current-frame
  386. (our-frame? (frame-matcher proc)))
  387. (arg-check proc procedure?)
  388. (arg-check enter-handler procedure?)
  389. (arg-check return-handler procedure?)
  390. (arg-check abort-handler procedure?)
  391. (let ((exit-trap #f))
  392. (define (return-hook frame)
  393. (exit-trap frame) ; disable the return/abort trap.
  394. (set! exit-trap #f)
  395. (return-handler frame))
  396. (define (abort-hook frame)
  397. (exit-trap frame) ; disable the return/abort trap.
  398. (set! exit-trap #f)
  399. (abort-handler frame))
  400. (define (apply-hook frame)
  401. (if (and (not exit-trap) (our-frame? frame))
  402. (begin
  403. (enter-handler frame)
  404. (set! exit-trap
  405. (trap-frame-finish frame return-hook abort-hook)))))
  406. (new-enabled-trap
  407. current-frame
  408. (lambda (frame)
  409. (vm-add-apply-hook! apply-hook))
  410. (lambda (frame)
  411. (if exit-trap
  412. (abort-hook frame))
  413. (set! exit-trap #f)
  414. (vm-remove-apply-hook! apply-hook)))))
  415. ;; Trapping all procedure calls within a dynamic extent, recording the
  416. ;; depth of the call stack relative to the original procedure.
  417. ;;
  418. (define* (trap-calls-in-dynamic-extent proc apply-handler return-handler
  419. #:key current-frame
  420. (our-frame? (frame-matcher proc)))
  421. (arg-check proc procedure?)
  422. (arg-check apply-handler procedure?)
  423. (arg-check return-handler procedure?)
  424. (let ((*stack* '()))
  425. (define (trace-return frame)
  426. (let ((fp* (frame-address frame)))
  427. (let lp ((stack *stack*))
  428. (match stack
  429. (() (values))
  430. ((fp . stack)
  431. (cond
  432. ((> fp fp*)
  433. (set! *stack* stack)
  434. (lp stack))
  435. ((= fp fp*) (set! *stack* stack))
  436. ((< fp fp*) (values)))))))
  437. (return-handler frame (1+ (length *stack*))))
  438. (define (trace-apply frame)
  439. (let ((fp* (frame-address frame)))
  440. (define (same-fp? fp) (= fp fp*))
  441. (define (newer-fp? fp) (> fp fp*))
  442. (let lp ((stack *stack*))
  443. (match stack
  444. (((? same-fp?) . stack)
  445. ;; A tail call, nothing to do.
  446. (values))
  447. (((? newer-fp?) . stack)
  448. ;; Unless there are continuations, we shouldn't get here.
  449. (set! *stack* stack)
  450. (lp stack))
  451. (stack
  452. (set! *stack* (cons fp* stack))))))
  453. (apply-handler frame (length *stack*)))
  454. (define (enter frame)
  455. (vm-add-return-hook! trace-return)
  456. (vm-add-apply-hook! trace-apply))
  457. (define (leave frame)
  458. (vm-remove-return-hook! trace-return)
  459. (vm-remove-apply-hook! trace-apply))
  460. (define (return frame)
  461. (leave frame))
  462. (define (abort frame)
  463. (leave frame))
  464. (trap-in-dynamic-extent proc enter return abort
  465. #:current-frame current-frame
  466. #:our-frame? our-frame?)))
  467. ;; Trapping all retired intructions within a dynamic extent.
  468. ;;
  469. (define* (trap-instructions-in-dynamic-extent proc next-handler
  470. #:key current-frame
  471. (our-frame? (frame-matcher proc)))
  472. (arg-check proc procedure?)
  473. (arg-check next-handler procedure?)
  474. (let ()
  475. (define (trace-next frame)
  476. (next-handler frame))
  477. (define (enter frame)
  478. (vm-add-next-hook! trace-next))
  479. (define (leave frame)
  480. (vm-remove-next-hook! trace-next))
  481. (define (return frame)
  482. (leave frame))
  483. (define (abort frame)
  484. (leave frame))
  485. (trap-in-dynamic-extent proc enter return abort
  486. #:current-frame current-frame
  487. #:our-frame? our-frame?)))
  488. ;; Traps calls and returns for a given procedure, keeping track of the call depth.
  489. ;;
  490. (define (trap-calls-to-procedure proc apply-handler return-handler)
  491. (arg-check proc procedure?)
  492. (arg-check apply-handler procedure?)
  493. (arg-check return-handler procedure?)
  494. (let ((pending-finish-traps '())
  495. (last-fp #f))
  496. (define (apply-hook frame)
  497. (let ((depth (length pending-finish-traps)))
  498. (apply-handler frame depth)
  499. (if (not (eqv? (frame-address frame) last-fp))
  500. (let ((finish-trap #f))
  501. (define (frame-finished frame)
  502. (finish-trap frame) ;; disables the trap.
  503. (set! pending-finish-traps
  504. (delq finish-trap pending-finish-traps))
  505. (set! finish-trap #f))
  506. (define (return-hook frame)
  507. (frame-finished frame)
  508. (return-handler frame depth))
  509. ;; FIXME: abort handler?
  510. (define (abort-hook frame)
  511. (frame-finished frame))
  512. (set! finish-trap
  513. (trap-frame-finish frame return-hook abort-hook))
  514. (set! pending-finish-traps
  515. (cons finish-trap pending-finish-traps))))))
  516. ;; The basic idea is that we install one trap that fires for calls,
  517. ;; but that each call installs its own finish trap. Those finish
  518. ;; traps remove themselves as their frames finish or abort.
  519. ;;
  520. ;; However since to the outside world we present the interface of
  521. ;; just being one trap, disabling this calls-to-procedure trap
  522. ;; should take care of disabling all of the pending finish traps. We
  523. ;; keep track of pending traps through the pending-finish-traps
  524. ;; list.
  525. ;;
  526. ;; So since we know that the trap-at-procedure will be enabled, and
  527. ;; thus returning a disable closure, we make sure to wrap that
  528. ;; closure in something that will disable pending finish traps.
  529. (define (with-pending-finish-disablers trap)
  530. (define (with-pending-finish-enablers trap)
  531. (lambda* (#:optional frame)
  532. (with-pending-finish-disablers (trap frame))))
  533. (lambda* (#:optional frame)
  534. (for-each (lambda (disable) (disable frame))
  535. pending-finish-traps)
  536. (set! pending-finish-traps '())
  537. (with-pending-finish-enablers (trap frame))))
  538. (with-pending-finish-disablers
  539. (trap-at-procedure-call proc apply-hook))))
  540. ;; Trap when the source location changes.
  541. ;;
  542. (define (trap-matching-instructions frame-pred handler)
  543. (arg-check frame-pred procedure?)
  544. (arg-check handler procedure?)
  545. (let ()
  546. (define (next-hook frame)
  547. (if (frame-pred frame)
  548. (handler frame)))
  549. (new-enabled-trap
  550. #f
  551. (lambda (frame)
  552. (vm-add-next-hook! next-hook))
  553. (lambda (frame)
  554. (vm-remove-next-hook! next-hook)))))