Commit | Line | Data |
---|---|---|
af988bbf | 1 | ;;; Guile VM debugging facilities |
ac99cb0c | 2 | |
a589525d | 3 | ;;; Copyright (C) 2001, 2009, 2010 Free Software Foundation, Inc. |
587cd3bf LC |
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 | |
ac99cb0c KN |
18 | |
19 | ;;; Code: | |
20 | ||
af988bbf | 21 | (define-module (system vm debug) |
391d2902 | 22 | #:use-module (system base pmatch) |
8239263f | 23 | #:use-module (system base syntax) |
1a1a10d3 AW |
24 | #:use-module (system vm vm) |
25 | #:use-module (system vm frame) | |
f6fe5fe2 AW |
26 | #:use-module (ice-9 rdelim) |
27 | #:use-module (ice-9 pretty-print) | |
1a1a10d3 | 28 | #:use-module (ice-9 format) |
e106eca6 | 29 | #:use-module ((system vm inspect) #:select ((inspect . %inspect))) |
f6fe5fe2 | 30 | #:use-module (system vm program) |
1ad7fef5 | 31 | #:export (run-debugger debug-pre-unwind-handler)) |
af988bbf KN |
32 | |
33 | \f | |
f6fe5fe2 AW |
34 | (define (reverse-hashq h) |
35 | (let ((ret (make-hash-table))) | |
36 | (hash-for-each | |
37 | (lambda (k v) | |
38 | (hashq-set! ret v (cons k (hashq-ref ret v '())))) | |
39 | h) | |
40 | ret)) | |
41 | ||
42 | (define (catch-bad-arguments thunk bad-args-thunk) | |
43 | (catch 'wrong-number-of-args | |
44 | (lambda () | |
45 | (catch 'keyword-argument-error | |
46 | thunk | |
47 | (lambda (k . args) | |
48 | (bad-args-thunk)))) | |
49 | (lambda (k . args) | |
50 | (bad-args-thunk)))) | |
51 | ||
52 | (define (read-args prompt) | |
53 | (define (read* reader) | |
54 | (repl-reader prompt reader)) | |
55 | (define (next) | |
56 | (read* read-char)) | |
57 | (define (cmd chr) | |
58 | (cond | |
59 | ((eof-object? chr) (list chr)) | |
60 | ((char=? chr #\newline) (cmd (next))) | |
61 | ((char-whitespace? chr) (cmd (next))) | |
62 | (else | |
63 | (unread-char chr) | |
64 | (let ((tok (read* read))) | |
65 | (args (list tok) (next)))))) | |
66 | (define (args out chr) | |
67 | (cond | |
68 | ((eof-object? chr) (reverse out)) | |
69 | ((char=? chr #\newline) (reverse out)) | |
70 | ((char-whitespace? chr) (args out (next))) | |
71 | (else | |
72 | (unread-char chr) | |
73 | (let ((tok (read* read))) | |
74 | (args (cons tok out) (next)))))) | |
75 | (cmd (next))) | |
76 | ||
0c2a05c3 AW |
77 | (define* (print-locals frame #:optional (port (current-output-port)) |
78 | #:key (width 72) (per-line-prefix "")) | |
79 | (let ((bindings (frame-bindings frame))) | |
80 | (cond | |
81 | ((null? bindings) | |
82 | (format port "~aNo local variables.~%" per-line-prefix)) | |
83 | (else | |
84 | (format port "~aLocal variables:~%" per-line-prefix) | |
85 | (for-each | |
86 | (lambda (binding) | |
87 | (format port "~a~4d ~a~:[~; (boxed)~] = ~v:@y\n" | |
88 | per-line-prefix | |
89 | (binding:index binding) | |
90 | (binding:name binding) | |
91 | (binding:boxed? binding) | |
92 | width | |
93 | (let ((x (frame-local-ref frame (binding:index binding)))) | |
94 | (if (binding:boxed? binding) | |
95 | (variable-ref x) | |
96 | x)))) | |
97 | (frame-bindings frame)))))) | |
98 | ||
06dcb9df AW |
99 | (define* (print-frames frames |
100 | #:optional (port (current-output-port)) | |
101 | #:key (width 72) (full? #f) (forward? #f) count) | |
102 | (let* ((len (vector-length frames)) | |
103 | (lower-idx (if (or (not count) (positive? count)) | |
104 | 0 | |
105 | (max 0 (+ len count)))) | |
106 | (upper-idx (if (and count (negative? count)) | |
107 | (1- len) | |
108 | (1- (if count (min count len) len)))) | |
109 | (inc (if forward? 1 -1))) | |
110 | (let lp ((i (if forward? lower-idx upper-idx)) | |
111 | (last-file "")) | |
112 | (if (<= lower-idx i upper-idx) | |
113 | (let* ((frame (vector-ref frames i)) | |
114 | (source (frame-source frame)) | |
115 | (file (and source | |
116 | (or (source:file source) | |
117 | "current input"))) | |
118 | (line (and=> source source:line))) | |
119 | (if (and file (not (equal? file last-file))) | |
120 | (format port "~&In ~a:~&" file)) | |
121 | (format port "~:[~*~6_~;~5d:~]~3d ~v:@y~%" line line | |
122 | i width (frame-call-representation frame)) | |
123 | (if full? | |
124 | (print-locals frame #:width width | |
125 | #:per-line-prefix " ")) | |
126 | (lp (+ i inc) (or file last-file))))))) | |
0c2a05c3 AW |
127 | |
128 | ||
af988bbf KN |
129 | ;;; |
130 | ;;; Debugger | |
131 | ;;; | |
132 | ||
1ad7fef5 | 133 | (define-record <debugger> vm level breakpoints module) |
af988bbf | 134 | |
1ad7fef5 AW |
135 | (define (make-debugger-module) |
136 | (let ((m (make-fresh-user-module))) | |
137 | m)) | |
138 | ||
139 | (define vm-debugger | |
140 | (let ((prop (make-object-property))) | |
141 | (lambda (vm) | |
142 | (or (prop vm) | |
f6fe5fe2 | 143 | (let ((debugger (make-debugger vm 0 '() (make-debugger-module)))) |
1ad7fef5 AW |
144 | (set! (prop vm) debugger) |
145 | debugger))))) | |
146 | ||
06dcb9df | 147 | (define* (run-debugger stack frames i #:optional (vm (the-vm))) |
1ad7fef5 AW |
148 | (let* ((db (vm-debugger vm)) |
149 | (level (debugger-level db))) | |
f6fe5fe2 AW |
150 | (dynamic-wind |
151 | (lambda () (set! (debugger-level db) (1+ level))) | |
06dcb9df | 152 | (lambda () (debugger-repl db stack frames i)) |
f6fe5fe2 | 153 | (lambda () (set! (debugger-level db) level))))) |
1ad7fef5 | 154 | |
06dcb9df AW |
155 | (define (debugger-repl db stack frames index) |
156 | (let ((top (vector-ref frames 0)) | |
157 | (cur (vector-ref frames index)) | |
e106eca6 AW |
158 | (level (debugger-level db)) |
159 | (last #f)) | |
d7a4096d | 160 | (define (frame-at-index idx) |
06dcb9df AW |
161 | (and (< idx (vector-length frames)) |
162 | (vector-ref frames idx))) | |
d7a4096d | 163 | (define (show-frame) |
1c5e8122 AW |
164 | ;; #2 0x009600e0 in do_std_select (args=0xbfffd9e0) at threads.c:1668 |
165 | ;; 1668 select (select_args->nfds, | |
166 | (format #t "#~2a 0x~8,'0x in ~60@y~%" | |
167 | index | |
168 | (frame-instruction-pointer cur) | |
169 | (frame-call-representation cur))) | |
f6fe5fe2 AW |
170 | |
171 | (define-syntax define-command | |
172 | (syntax-rules () | |
173 | ((_ ((mod cname alias ...) . args) body ...) | |
174 | (define cname | |
175 | (let ((c (lambda* args body ...))) | |
176 | (set-procedure-property! c 'name 'cname) | |
177 | (module-define! mod 'cname c) | |
178 | (module-add! mod 'alias (module-local-variable mod 'cname)) | |
179 | ... | |
180 | c))))) | |
181 | ||
182 | (let ((commands (make-module))) | |
183 | (define (prompt) | |
184 | (format #f "~a~a debug> " | |
185 | (if (= level 1) | |
186 | "" | |
187 | (format #f "~a:" level)) | |
188 | index)) | |
189 | ||
190 | (define (print* . vals) | |
191 | (define (print x) | |
192 | (run-hook before-print-hook x) | |
e106eca6 | 193 | (set! last x) |
f6fe5fe2 AW |
194 | (pretty-print x)) |
195 | (if (and (pair? vals) | |
196 | (not (and (null? (cdr vals)) | |
197 | (unspecified? (car vals))))) | |
198 | (for-each print vals))) | |
199 | ||
0c2a05c3 AW |
200 | (define-command ((commands backtrace bt) #:optional count |
201 | #:key (width 72) full?) | |
06dcb9df AW |
202 | "Print a backtrace of all stack frames, or innermost COUNT frames. |
203 | If COUNT is negative, the last COUNT frames will be shown." | |
204 | (print-frames frames | |
205 | #:count count | |
0c2a05c3 AW |
206 | #:width width |
207 | #:full? full?)) | |
d7a4096d AW |
208 | |
209 | (define-command ((commands up) #:optional (count 1)) | |
210 | "Select and print stack frames that called this one. | |
211 | An argument says how many frames up to go" | |
06dcb9df AW |
212 | (cond |
213 | ((or (not (integer? count)) (<= count 0)) | |
214 | (format #t "Invalid argument to `up': expected a positive integer for COUNT.~%")) | |
215 | ((>= (+ count index) (vector-length frames)) | |
216 | (cond | |
217 | ((= index (1- (vector-length frames))) | |
218 | (format #t "Already at outermost frame.\n")) | |
219 | (else | |
220 | (set! index (1- (vector-length frames))) | |
221 | (set! cur (vector-ref frames index)) | |
222 | (show-frame)))) | |
223 | (else | |
224 | (set! index (+ count index)) | |
225 | (set! cur (vector-ref frames index)) | |
226 | (show-frame)))) | |
227 | ||
d7a4096d AW |
228 | (define-command ((commands down) #:optional (count 1)) |
229 | "Select and print stack frames called by this one. | |
230 | An argument says how many frames down to go" | |
231 | (cond | |
232 | ((or (not (integer? count)) (<= count 0)) | |
233 | (format #t "Invalid argument to `down': expected a positive integer for COUNT.~%")) | |
06dcb9df AW |
234 | ((< (- index count) 0) |
235 | (cond | |
236 | ((zero? index) | |
237 | (format #t "Already at innermost frame.\n")) | |
238 | (else | |
239 | (set! index 0) | |
240 | (set! cur (vector-ref frames index)) | |
241 | (show-frame)))) | |
d7a4096d | 242 | (else |
06dcb9df AW |
243 | (set! index (- index count)) |
244 | (set! cur (vector-ref frames index)) | |
d7a4096d | 245 | (show-frame)))) |
06dcb9df | 246 | |
d7a4096d AW |
247 | (define-command ((commands frame f) #:optional idx) |
248 | "Show the selected frame. | |
249 | With an argument, select a frame by index, then show it." | |
250 | (cond | |
251 | (idx | |
252 | (cond | |
253 | ((or (not (integer? idx)) (< idx 0)) | |
254 | (format #t "Invalid argument to `frame': expected a non-negative integer for IDX.~%")) | |
255 | ((frame-at-index idx) | |
256 | => (lambda (f) | |
257 | (set! cur f) | |
258 | (set! index idx) | |
259 | (show-frame))) | |
260 | (else | |
261 | (format #t "No such frame.~%")))) | |
262 | (else (show-frame)))) | |
263 | ||
e106eca6 AW |
264 | (define-command ((commands procedure proc)) |
265 | "Print the procedure for the selected frame." | |
266 | (print* (frame-procedure cur))) | |
267 | ||
268 | (define-command ((commands inspect i)) | |
269 | "Launch the inspector on the last-printed object." | |
270 | (%inspect last)) | |
271 | ||
1c5e8122 AW |
272 | (define-command ((commands locals)) |
273 | "Show locally-bound variables in the selected frame." | |
0c2a05c3 | 274 | (print-locals cur)) |
1c5e8122 | 275 | |
f6fe5fe2 AW |
276 | (define-command ((commands quit q continue cont c)) |
277 | "Quit the debugger and let the program continue executing." | |
278 | (throw 'quit)) | |
279 | ||
f6fe5fe2 AW |
280 | (define-command ((commands help h ?) #:optional cmd) |
281 | "Show this help message." | |
282 | (let ((rhash (reverse-hashq (module-obarray commands)))) | |
283 | (define (help-cmd cmd) | |
284 | (let* ((v (module-local-variable commands cmd)) | |
285 | (p (variable-ref v)) | |
286 | (canonical-name (procedure-name p))) | |
287 | ;; la la la | |
288 | (format #t "~a~{ ~:@(~a~)~}~?~%~a~&~%" | |
289 | canonical-name (program-lambda-list p) | |
290 | "~#[~:;~40t(aliases: ~@{~a~^, ~})~]" | |
291 | (delq canonical-name (hashq-ref rhash v)) | |
292 | (procedure-documentation p)))) | |
293 | (cond | |
294 | (cmd | |
295 | (cond | |
296 | ((and (symbol? cmd) (module-local-variable commands cmd)) | |
297 | (help-cmd cmd)) | |
298 | (else | |
299 | (format #t "Invalid command ~s.~%" cmd) | |
300 | (format #t "Try `help' for a list of commands~%")))) | |
301 | (else | |
302 | (let ((names (sort | |
303 | (hash-map->list | |
304 | (lambda (k v) | |
305 | (procedure-name (variable-ref k))) | |
306 | rhash) | |
307 | (lambda (x y) | |
308 | (string<? (symbol->string x) | |
309 | (symbol->string y)))))) | |
310 | (format #t "Available commands:~%~%") | |
311 | (for-each help-cmd names)))))) | |
312 | ||
313 | (define (handle cmd . args) | |
314 | (cond | |
315 | ((and (symbol? cmd) | |
316 | (module-local-variable commands cmd)) | |
317 | => (lambda (var) | |
318 | (let ((proc (variable-ref var))) | |
319 | (catch-bad-arguments | |
320 | (lambda () | |
321 | (apply (variable-ref var) args)) | |
322 | (lambda () | |
d7a4096d AW |
323 | (format (current-error-port) |
324 | "Invalid arguments to ~a. Try `help ~a'.~%" | |
325 | (procedure-name proc) (procedure-name proc))))))) | |
f6fe5fe2 | 326 | ((and (integer? cmd) (exact? cmd)) |
2d8c757c | 327 | (frame cmd)) |
f6fe5fe2 AW |
328 | ((eof-object? cmd) |
329 | (newline) | |
330 | (throw 'quit)) | |
331 | (else | |
332 | (format (current-error-port) | |
333 | "~&Unknown command: ~a. Try `help'.~%" cmd) | |
334 | *unspecified*))) | |
335 | ||
336 | (catch 'quit | |
337 | (lambda () | |
338 | (let loop () | |
d7a4096d AW |
339 | (apply |
340 | handle | |
341 | (save-module-excursion | |
342 | (lambda () | |
343 | (set-current-module commands) | |
344 | (read-args prompt)))) | |
f6fe5fe2 AW |
345 | (loop))) |
346 | (lambda (k . args) | |
347 | (apply values args)))))) | |
1ad7fef5 | 348 | |
d7a4096d | 349 | |
441891f3 | 350 | ;; TODO: |
1ad7fef5 AW |
351 | ;; |
352 | ;; eval expression in context of frame | |
1ad7fef5 | 353 | ;; set local variable in frame |
1ad7fef5 AW |
354 | ;; step until next instruction |
355 | ;; step until next function call/return | |
356 | ;; step until return from frame | |
357 | ;; step until different source line | |
358 | ;; step until greater source line | |
359 | ;; watch expression | |
360 | ;; break on a function | |
361 | ;; remove breakpoints | |
362 | ;; set printing width | |
363 | ;; display a truncated backtrace | |
364 | ;; go to a frame by index | |
365 | ;; (reuse gdb commands perhaps) | |
1ad7fef5 AW |
366 | ;; disassemble a function |
367 | ;; disassemble the current function | |
368 | ;; inspect any object | |
369 | ;; hm, trace via reassigning global vars. tricksy. | |
370 | ;; (state associated with vm ?) | |
ac99cb0c | 371 | |
06dcb9df AW |
372 | (define (stack->vector stack) |
373 | (let* ((len (stack-length stack)) | |
374 | (v (make-vector len))) | |
375 | (if (positive? len) | |
376 | (let lp ((i 0) (frame (stack-ref stack 0))) | |
377 | (if (< i len) | |
378 | (begin | |
379 | (vector-set! v i frame) | |
380 | (lp (1+ i) (frame-previous frame)))))) | |
381 | v)) | |
382 | ||
1ad7fef5 | 383 | (define (debug-pre-unwind-handler key . args) |
06dcb9df AW |
384 | ;; Narrow the stack by three frames: make-stack, this one, and the throw |
385 | ;; handler. | |
386 | (cond | |
387 | ((make-stack #t 3) => | |
388 | (lambda (stack) | |
389 | (pmatch args | |
390 | ((,subr ,msg ,args . ,rest) | |
391 | (format #t "Throw to key `~a':\n" key) | |
392 | (display-error stack (current-output-port) subr msg args rest)) | |
393 | (else | |
394 | (format #t "Throw to key `~a' with args `~s'." key args))) | |
395 | (format #t "Entering the debugger. Type `bt' for a backtrace or `c' to continue.\n") | |
396 | (run-debugger stack | |
397 | (stack->vector | |
398 | ;; by default, narrow to the most recent start-stack | |
399 | (make-stack (stack-ref stack 0) 0 | |
400 | (and (pair? (fluid-ref %stacks)) | |
401 | (cdar (fluid-ref %stacks))))) | |
402 | 0)))) | |
37e9bc8a | 403 | (save-stack debug-pre-unwind-handler) |
1ad7fef5 | 404 | (apply throw key args)) |