Newer
Older
(when (assoc ,name *debug-commands* :test #'string=)
(setf *debug-commands*
(remove ,name *debug-commands* :key #'car :test #'string=)))
(defun ,fun-name ,args
(unless *in-the-debugger*
(error "Invoking debugger command while outside the debugger."))
,@body)
(push (cons ,name #',fun-name) *debug-commands*)
',fun-name)))
;;; DEF-DEBUG-COMMAND-ALIAS -- Internal.
;;;
(defun def-debug-command-alias (new-name existing-name)
(let ((pair (assoc existing-name *debug-commands* :test #'string=)))
(unless pair (error "Unknown debug command name -- ~S" existing-name))
(push (cons new-name (cdr pair)) *debug-commands*))
new-name)
;;; DEBUG-COMMAND-P -- Internal.
;;;
;;; This takes a symbol and uses its name to find a debugger command, using
;;; initial substring matching. It returns the command function if form
;;; identifies only one command, but if form is ambiguous, this returns a list
;;; of the command names. If there are no matches, this returns nil. Whenever
;;; the loop that looks for a set of possibilities encounters an exact name
;;; match, we return that command function immediately.
;;;
(if (or (symbolp form) (integerp form))
(let* ((name
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
(if (symbolp form)
(symbol-name form)
(format nil "~d" form)))
(len (length name))
(res nil))
(declare (simple-string name)
(fixnum len)
(list res))
;;
;; Find matching commands, punting if exact match.
(flet ((match-command (ele)
(let* ((str (car ele))
(str-len (length str)))
(declare (simple-string str)
(fixnum str-len))
(cond ((< str-len len))
((= str-len len)
(when (string= name str :end1 len :end2 len)
(return-from debug-command-p (cdr ele))))
((string= name str :end1 len :end2 len)
(push ele res))))))
(mapc #'match-command *debug-commands*)
(mapc #'match-command other-commands))
;;
((= (length res) 1)
(cdar res))
(t ;Just return the names.
(do ((cmds res (cdr cmds)))
((not cmds) res)
(setf (car cmds) (caar cmds))))))))
;;; Returns a list of debug commands (in the same format as *debug-commands*)
;;; that invoke each active restart.
;;;
;;; Two commands are made for each restart: one for the number, and one for
;;; the restart name (unless it's been shadowed by an earlier restart of the
;;; same name, or it is nil).
(defun make-restart-commands (&optional (restarts *debug-restarts*))
(let ((commands)
(num 0)) ; better be the same as show-restarts!
(let ((name (string (restart-name restart))))
(let ((restart-fun
#'(lambda () (invoke-restart-interactively restart))))
(push (cons (format nil "~d" num) restart-fun) commands)
(unless (or (null (restart-name restart))
(find name commands :key #'car :test #'string=))
(push (cons name restart-fun) commands))))
commands))
;;;
;;; Frame changing commands.
;;;
(def-debug-command "UP" ()
(let ((next (di:frame-up *current-frame*)))
(cond (next
(setf *current-frame* next)
(print-frame-call next))
(t
(format t "~&Top of stack.")))))
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
(def-debug-command "DOWN" ()
(let ((next (di:frame-down *current-frame*)))
(cond (next
(setf *current-frame* next)
(print-frame-call next))
(t
(format t "~&Bottom of stack.")))))
(def-debug-command-alias "D" "DOWN")
(def-debug-command "TOP" ()
(do ((prev *current-frame* lead)
(lead (di:frame-up *current-frame*) (di:frame-up lead)))
((null lead)
(setf *current-frame* prev)
(print-frame-call prev))))
(def-debug-command "BOTTOM" ()
(do ((prev *current-frame* lead)
(lead (di:frame-down *current-frame*) (di:frame-down lead)))
((null lead)
(setf *current-frame* prev)
(print-frame-call prev))))
(n (read-prompting-maybe "Frame number: ")))
(let ((current (di:frame-number *current-frame*)))
(setf *current-frame*
(do ((prev *current-frame* lead)
(lead (di:frame-down *current-frame*)
((null lead)
(princ "Bottom of stack encountered.")
prev)
(when (= n (di:frame-number prev))
(return prev))))))
(print-frame-call
(setf *current-frame*
(do ((prev *current-frame* lead)
(lead (di:frame-up *current-frame*)
(di:frame-up lead)))
((null lead)
(princ "Top of stack encountered.")
prev)
(when (= n (di:frame-number prev))
(return prev)))))))))
(def-debug-command-alias "F" "FRAME")
;;; In and Out commands.
(def-debug-command "QUIT" ()
(continue *debug-condition*)
(def-debug-command "RESTART" ()
(let ((num (read-if-available :prompt)))
(when (eq num :prompt)
(show-restarts *debug-restarts*)
(write-string "Restart: ")
(force-output)
(setf num (read *standard-input*)))
(let ((restart (typecase num
(unsigned-byte
(nth num *debug-restarts*))
(symbol
(find num *debug-restarts* :key #'restart-name
:test #'(lambda (sym1 sym2)
(string= (symbol-name sym1)
(symbol-name sym2)))))
(format t "~S is invalid as a restart name.~%" num)
(return-from restart-debug-command nil)))))
(if restart
(invoke-restart-interactively restart)
"This controls how many lines the debugger's help command prints before
(def-debug-command "HELP" ()
(let ((start (1+ end))
(count *help-line-scroll-count*))
(loop
(setf end (position #\newline debug-help-string :start (1+ end)))
(cond ((or (not end) (= end len-1))
(setf end len)
(return))
((or (zerop (decf count)) (= end len))
(return))))
(write-string debug-help-string *standard-output*
:start start :end end))
(when (= end len) (return))
(format t "~%[RETURN FOR MORE, Q TO QUIT HELP TEXT]: ")
(force-output)
(let ((res (read-line)))
(when (or (string= res "q") (string= res "Q"))
(return))))))
(def-debug-command-alias "?" "HELP")
(def-debug-command "ERROR" ()
(format t "~A~%" *debug-condition*)
(show-restarts *debug-restarts*))
(def-debug-command "BACKTRACE" ()
(backtrace (read-if-available most-positive-fixnum)))
(def-debug-command "PRINT" ()
(print-frame-call *current-frame*))
(def-debug-command-alias "P" "PRINT")
(print-frame-call *current-frame* :print-level nil :print-length nil
:verbosity (read-if-available 2)))
(def-debug-command-alias "PP" "VPRINT")
(def-debug-command "LIST-LOCALS" ()
(let ((d-fun (di:frame-debug-function *current-frame*)))
(if (di:debug-variable-info-available d-fun)
(let ((*print-level* (or *debug-print-level* *print-level*))
(*print-length* (or *debug-print-length* *print-length*))
(location (di:frame-code-location *current-frame*))
(prefix (read-if-available nil))
(any-p nil)
(any-valid-p nil))
(dolist (v (di:ambiguous-debug-variables
(if prefix (string prefix) "")))
(setf any-p t)
(when (eq (di:debug-variable-validity v location) :valid)
(setf any-valid-p t)
(format t "~S~:[#~D~;~*~] = ~S~%"
(di:debug-variable-symbol v)
(zerop (di:debug-variable-id v))
(di:debug-variable-id v)
(di:debug-variable-value v *current-frame*))))
(cond
((not any-p)
(format t "No local variables ~@[starting with ~A ~]~
in function."
prefix))
((not any-valid-p)
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
(format t "All variables ~@[starting with ~A ~]currently ~
have invalid values."
prefix))))
(write-line "No variable information available."))))
(def-debug-command-alias "L" "LIST-LOCALS")
(def-debug-command "SOURCE" ()
(fresh-line)
(print-code-location-source-form (di:frame-code-location *current-frame*)
(read-if-available 0)))
(def-debug-command "VSOURCE" ()
(fresh-line)
(print-code-location-source-form (di:frame-code-location *current-frame*)
(read-if-available 0)
t))
;;;; Source location printing:
;;; We cache a stream to the last valid file debug source so that we won't have
;;; to repeatedly open the file.
;;;
(defvar *cached-debug-source* nil)
(declaim (type (or di:debug-source null) *cached-debug-source*))
(defvar *cached-source-stream* nil)
(declaim (type (or stream null) *cached-source-stream*))
;;; To suppress the read-time evaluation #. macro during source read
;;; the *readtable* is modified. The *readtable* is cached to avoid
;;; copying it each time, and invalidated when the
;;; *cached-debug-source* has changed.
(defvar *cached-readtable* nil)
(declaim (type (or readtable null) *cached-readtable*))
(pushnew #'(lambda ()
(setq *cached-debug-source* nil *cached-source-stream* nil
*cached-readtable* nil))
;;; We also cache the last top-level form that we printed a source for so that
;;;
(defvar *cached-top-level-form-offset* nil)
(declaim (type (or kernel:index null) *cached-top-level-form-offset*))
(defvar *cached-top-level-form*)
(defvar *cached-form-number-translations*)
;;; GET-TOP-LEVEL-FORM -- Internal
;;;
;;; Given a code location, return the associated form-number translations
;;; and the actual top-level form. We check our cache --- if there is a miss,
;;; we dispatch on the kind of the debug source.
;;;
(defun get-top-level-form (location)
(let ((d-source (di:code-location-debug-source location)))
(if (and (eq d-source *cached-debug-source*)
(eql (di:code-location-top-level-form-offset location)
*cached-top-level-form-offset*))
(values *cached-form-number-translations* *cached-top-level-form*)
(res
(ecase (di:debug-source-from d-source)
(:file (get-file-top-level-form location))
((:lisp :stream)
(svref (di:debug-source-name d-source) offset)))))
(setq *cached-top-level-form-offset* offset)
(values (setq *cached-form-number-translations*
(setq *cached-top-level-form* res))))))
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
;;; GET-FILE-TOP-LEVEL-FORM -- Internal.
;;;
;;; Locates the source file (if it still exists) and grabs the top-level form.
;;; If the file is modified, we use the top-level-form offset instead of the
;;; recorded character offset.
;;;
(defun get-file-top-level-form (location)
(let* ((d-source (di:code-location-debug-source location))
(tlf-offset (di:code-location-top-level-form-offset location))
(local-tlf-offset (- tlf-offset
(di:debug-source-root-number d-source)))
(char-offset
(aref (or (di:debug-source-start-positions d-source)
(error "No start positions map."))
local-tlf-offset))
(name (di:debug-source-name d-source)))
(unless (eq d-source *cached-debug-source*)
(unless (and *cached-source-stream*
(equal (pathname *cached-source-stream*)
(pathname name)))
(setq *cached-readtable* nil)
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
(when *cached-source-stream* (close *cached-source-stream*))
(setq *cached-source-stream* (open name :if-does-not-exist nil))
(unless *cached-source-stream*
(error "Source file no longer exists:~% ~A." (namestring name)))
(format t "~%; File: ~A~%" (namestring name)))
(setq *cached-debug-source*
(if (= (di:debug-source-created d-source) (file-write-date name))
d-source nil)))
(cond
((eq *cached-debug-source* d-source)
(file-position *cached-source-stream* char-offset))
(t
(format t "~%; File has been modified since compilation:~%; ~A~@
; Using form offset instead of character position.~%"
(namestring name))
(file-position *cached-source-stream* 0)
(let ((*read-suppress* t))
(dotimes (i local-tlf-offset)
(read *cached-source-stream*)))))
(unless *cached-readtable*
(setq *cached-readtable* (copy-readtable))
(set-dispatch-macro-character
#\# #\.
#'(lambda (stream sub-char &rest rest)
(declare (ignore rest sub-char))
(let ((token (read stream t nil t)))
(format nil "#.~s" token)))
*cached-readtable*))
(let ((*readtable* *cached-readtable*))
(read *cached-source-stream*))))
;;; PRINT-CODE-LOCATION-SOURCE-FORM -- Internal.
;;;
(defun print-code-location-source-form (location context &optional verbose)
(let* ((location (maybe-block-start-location location))
(*print-level* (if verbose
nil
(or *debug-print-level* *print-level*)))
(*print-length* (if verbose
nil
(or *debug-print-length* *print-length*)))
(form-num (di:code-location-form-number location)))
(multiple-value-bind (translations form)
(get-top-level-form location)
(unless (< form-num (length translations))
(error "Source path no longer exists."))
(prin1 (di:source-path-context form
(svref translations form-num)
context)))))
;;;
;;; Breakpoint and step commands.
;;;
;;; Steps to the next code-location
(def-debug-command "STEP" ()
(setf *number-of-steps* (read-if-available 1))
(set-step-breakpoint *current-frame*)
(continue *debug-condition*)
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
(error "Couldn't continue."))
;;; Lists possible breakpoint locations, which are active, and where go will
;;; continue. Sets *possible-breakpoints* to the code-locations which can then
;;; be used by sbreakpoint. Takes a function as an optional argument.
(def-debug-command "LIST-LOCATIONS" ()
(let ((df (read-if-available *default-breakpoint-debug-function*)))
(cond ((consp df)
(setf df (di:function-debug-function (eval df)))
(setf *default-breakpoint-debug-function* df))
((or (eq ':c df)
(not *default-breakpoint-debug-function*))
(setf df (di:frame-debug-function *current-frame*))
(setf *default-breakpoint-debug-function* df)))
(setf *possible-breakpoints* (possible-breakpoints df)))
(let ((continue-at (di:frame-code-location *current-frame*)))
(let ((active (location-in-list *default-breakpoint-debug-function*
*breakpoints* :function-start))
(here (di:code-location=
(di:debug-function-start-location
*default-breakpoint-debug-function*) continue-at)))
(when (or active here)
(format t "::FUNCTION-START ")
(when active (format t " *Active*"))
(when here (format t " *Continue here*"))))
(let ((prev-location nil)
(prev-num 0)
(this-num 0))
(flet ((flush ()
(when prev-location
(let ((this-num (1- this-num)))
(if (= prev-num this-num)
(format t "~&~D: " prev-num)
(format t "~&~D-~D: " prev-num this-num)))
(print-code-location-source-form prev-location 0)
(when *print-location-kind*
(format t "~S " (di:code-location-kind prev-location)))
(when (location-in-list prev-location *breakpoints*)
(format t " *Active*"))
(when (di:code-location= prev-location continue-at)
(format t " *Continue here*")))))
(dolist (code-location *possible-breakpoints*)
(when (or *print-location-kind*
(location-in-list code-location *breakpoints*)
(di:code-location= code-location continue-at)
(not prev-location)
(not (eq (di:code-location-debug-source code-location)
(di:code-location-debug-source prev-location)))
(not (eq (di:code-location-top-level-form-offset
code-location)
(di:code-location-top-level-form-offset
prev-location)))
(not (eq (di:code-location-form-number code-location)
(di:code-location-form-number prev-location))))
(flush)
(setq prev-location code-location prev-num this-num))
(incf this-num))))
(when (location-in-list *default-breakpoint-debug-function* *breakpoints*
:function-end)
(format t "~&::FUNCTION-END *Active* "))))
(def-debug-command-alias "LL" "LIST-LOCATIONS")
;;; set breakpoint at # given
(def-debug-command "BREAKPOINT" ()
(let ((index (read-prompting-maybe "Location number, :start, or :end: "))
(break t)
(condition t)
(print nil)
(print-functions nil)
(function nil)
(bp)
(place *default-breakpoint-debug-function*))
(flet ((get-command-line ()
(let ((command-line nil)
(unique '(nil)))
(loop
(let ((next-input (read-if-available unique)))
(when (eq next-input unique) (return))
(push next-input command-line)))
(nreverse command-line)))
(set-vars-from-command-line (command-line)
(do ((arg (pop command-line) (pop command-line)))
((not arg))
(ecase arg
(:condition (setf condition (pop command-line)))
(:print (push (pop command-line) print))
(:break (setf break (pop command-line)))
(:function
(setf function (eval (pop command-line)))
(setf *default-breakpoint-debug-function*
(di:function-debug-function function))
(setf *possible-breakpoints*
(possible-breakpoints
*default-breakpoint-debug-function*))))))
(setup-function-start ()
:kind :function-start))
(setf break (di:preprocess-for-eval break code-loc))
(push (cons (di:preprocess-for-eval form code-loc) form)
print-functions))))
(setup-function-end ()
(setf bp
(di:make-breakpoint #'main-hook-function place
:kind :function-end))
(setf break
(coerce `(lambda (dummy)
(declare (ignore dummy)) ,break)
'function))
(setf condition (coerce `(lambda (dummy)
(declare (ignore dummy)) ,condition)
'function))
(dolist (form print)
(push (cons
(coerce `(lambda (dummy)
(declare (ignore dummy)) ,form) 'function)
form)
print-functions)))
(setup-code-location ()
(setf place (nth index *possible-breakpoints*))
(setf bp (di:make-breakpoint #'main-hook-function place
:kind :code-location))
(dolist (form print)
(push (cons
(di:preprocess-for-eval form place)
form)
print-functions))
(setf break (di:preprocess-for-eval break place))
(setf condition (di:preprocess-for-eval condition place))))
(set-vars-from-command-line (get-command-line))
(cond
((or (eq index :start) (eq index :s))
(setup-function-start))
((or (eq index :end) (eq index :e))
(setup-function-end))
(t
(setup-code-location)))
(di:activate-breakpoint bp)
(let* ((new-bp-info (create-breakpoint-info place bp index
:break break
:print print-functions
:condition condition))
(when old-bp-info
(di:deactivate-breakpoint (breakpoint-info-breakpoint old-bp-info))
(setf *breakpoints* (remove old-bp-info *breakpoints*))
(format t "Note: previous breakpoint removed.~%"))
(push new-bp-info *breakpoints*))
(print-breakpoint-info (first *breakpoints*))
(format t "~&Added."))))
(def-debug-command-alias "BP" "BREAKPOINT")
;;; list all breakpoints set
(def-debug-command "LIST-BREAKPOINTS" ()
(setf *breakpoints*
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
(sort *breakpoints* #'< :key #'breakpoint-info-breakpoint-number))
(dolist (info *breakpoints*)
(print-breakpoint-info info)))
(def-debug-command-alias "LB" "LIST-BREAKPOINTS")
(def-debug-command-alias "LBP" "LIST-BREAKPOINTS")
;;; remove breakpoint n or all if none given
(def-debug-command "DELETE-BREAKPOINT" ()
(let* ((index (read-if-available nil))
(bp-info
(find index *breakpoints* :key #'breakpoint-info-breakpoint-number)))
(cond (bp-info
(di:delete-breakpoint (breakpoint-info-breakpoint bp-info))
(setf *breakpoints* (remove bp-info *breakpoints*))
(format t "Breakpoint ~S removed.~%" index))
(index (format t "Breakpoint doesn't exist."))
(t
(dolist (ele *breakpoints*)
(di:delete-breakpoint (breakpoint-info-breakpoint ele)))
(setf *breakpoints* nil)
(format t "All breakpoints deleted.~%")))))
(def-debug-command-alias "DBP" "DELETE-BREAKPOINT")
;;;
;;; Miscellaneous commands.
;;;
(def-debug-command "FLUSH-ERRORS" ()
(if (setf *flush-debug-errors* (not *flush-debug-errors*))
(write-line "Errors now flushed.")
(write-line "Errors now create nested debug levels.")))
(def-debug-command "DESCRIBE" ()
(let* ((curloc (di:frame-code-location *current-frame*))
(debug-fun (di:code-location-debug-function curloc))
(function (di:debug-function-function debug-fun)))
(if function
(describe function)
(format t "Can't figure out the function for this frame."))))
;;;
;;; Editor commands.
;;;
(def-debug-command "EDIT-SOURCE" ()
(unless (ed::ts-stream-p *terminal-io*)
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
(error "The debugger's EDIT-SOURCE command only works in slave Lisps ~
connected to a Hemlock editor."))
(let* ((wire (ed::ts-stream-wire *terminal-io*))
(location (maybe-block-start-location
(di:frame-code-location *current-frame*)))
(d-source (di:code-location-debug-source location))
(name (di:debug-source-name d-source)))
(ecase (di:debug-source-from d-source)
(:file
(let* ((tlf-offset (di:code-location-top-level-form-offset location))
(local-tlf-offset (- tlf-offset
(di:debug-source-root-number d-source)))
(char-offset (aref (or (di:debug-source-start-positions d-source)
(error "No start positions map."))
local-tlf-offset)))
(wire:remote wire
(ed::edit-source-location (namestring name)
(di:debug-source-created d-source)
tlf-offset local-tlf-offset char-offset
(di:code-location-form-number location)))
(wire:wire-force-output wire)))
((:lisp :stream)
(wire:remote wire
(ed::cannot-edit-source-location))
(wire:wire-force-output wire)))))
;;;; Debug loop command utilities.
(defun read-prompting-maybe (prompt &optional (in *standard-input*)
(out *standard-output*))
(unless (ext:listen-skip-whitespace in)
(princ prompt out)
(force-output out))
(read in))
(defun read-if-available (default &optional (stream *standard-input*))
(if (ext:listen-skip-whitespace stream)
(read stream)
default))