0.9.16.38:
authorJuho Snellman <jsnell@iki.fi>
Mon, 18 Sep 2006 20:09:13 +0000 (20:09 +0000)
committerJuho Snellman <jsnell@iki.fi>
Mon, 18 Sep 2006 20:09:13 +0000 (20:09 +0000)
        Rewrite the single-stepper to solve the compilation/run-time
        performance and type-inference inaccuracy problems with the old
        approach. Also make some UI improvements to the stepper.

        * The IR1 stage no longer instruments the code. Instead it
          only detects function call forms which should (according to
          the policy) be steppable, and records a string
          representation of those forms in the matching combination
          nodes (to be stored in the debug-info).
        * Modify the function call vops to emit some instrumentation just
          before the actual call happens. This will check either the
          symbol-value of *STEPPING* (unithreaded) or the new STEPPING
          slot of threads (multithreaded) and trap if it's true. The
          trap handler will replace the closure / fdefn that was about
          to be called with a wrapper, which will signal a stepper
          condition and then call the original function.
        * Emit a similar bit of instrumentation before any call that
          got optimized to a simple VOP. The difference is that the
          only thing that the trap handler will do is to directly
          signal the condition.
        * The necessary VOP / runtime changes have only been done on
          x86, x86-64 and ppc so far. Alpha, Mips and Sparc should
          still compile, but the stepper won't work there.
        * Remove the old single-stepper REPL, and instead integrate the
          stepper into the debugger.
        * Add STEP-OUT functionality (stop stepping temporarily,
          resuming it once the current function returns).

40 files changed:
NEWS
build-order.lisp-expr
package-data-list.lisp-expr
src/code/condition.lisp
src/code/debug-info.lisp
src/code/debug-int.lisp
src/code/debug-var-io.lisp
src/code/debug.lisp
src/code/early-step.lisp [new file with mode: 0644]
src/code/step.lisp
src/code/target-thread.lisp
src/code/toplevel.lisp
src/compiler/alpha/call.lisp
src/compiler/alpha/parms.lisp
src/compiler/debug-dump.lisp
src/compiler/generic/objdef.lisp
src/compiler/generic/parms.lisp
src/compiler/ir1tran.lisp
src/compiler/ir2tran.lisp
src/compiler/mips/call.lisp
src/compiler/mips/parms.lisp
src/compiler/node.lisp
src/compiler/ppc/call.lisp
src/compiler/ppc/parms.lisp
src/compiler/sparc/call.lisp
src/compiler/sparc/parms.lisp
src/compiler/x86-64/call.lisp
src/compiler/x86-64/insts.lisp
src/compiler/x86-64/parms.lisp
src/compiler/x86/call.lisp
src/compiler/x86/parms.lisp
src/runtime/breakpoint.c
src/runtime/ppc-arch.c
src/runtime/thread.c
src/runtime/x86-64-arch.c
src/runtime/x86-arch.c
tests/debug.impure.lisp
tests/foreign-stack-alignment.impure.lisp
tests/step.impure.lisp [new file with mode: 0644]
version.lisp-expr

diff --git a/NEWS b/NEWS
index 6b3cd62..a6bcb69 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,8 @@ changes in sbcl-0.9.17 (0.9.99?) relative to sbcl-0.9.16:
     following unused symbols: *GC-NOTIFY-AFTER*, *GC-NOTIFY-BEFORE*,
     *GC-NOTIFY-STREAM*, *ERROR-PRINT-LENGTH*, *ERROR-PRINT-LEVEL*,
     *ERROR-PRINT-LINES*
+  * incompatible change: the single-stepper is no longer available 
+    on Alpha, Mips and Sparc platforms.
   * minor incompatible change: the direct superclasses of
     SB-MOP:FUNCALLABLE-STANDARD-OBJECT are (FUNCTION STANDARD-OBJECT),
     not (STANDARD-OBJECT FUNCTION).  This makes the
@@ -23,6 +25,10 @@ changes in sbcl-0.9.17 (0.9.99?) relative to sbcl-0.9.16:
     of the compiler. EVAL still uses the compiler by default, to switch it
     to use the interpreter, set the value of the variable 
     SB-EXT:*EVALUATOR-MODE* to :INTERPRET.
+  * minor incompatible change: the single-stepper REPL has been merged
+    with the normal debugger (see the "Stepping" heading of the debugger help
+    for more details). The debugger command STEP will no longer switch
+    to the single-stepper REPL. 
   * bug fix: ENOUGH-NAMESTRING on pathnames with no name and a pattern
     for a type now works.
   * bug fix: loading of default sysinit file works. (thanks to Leonid
@@ -38,6 +44,9 @@ changes in sbcl-0.9.17 (0.9.99?) relative to sbcl-0.9.16:
     Gracin).
   * bug fix: timers expiring in dead threads no longer cause a
     type-error (reported by Paul "Nonny Mouse"). 
+  * bug fix: thanks to more lightweight single-stepper instrumentation, 
+    code compiled with (DEBUG 3) will compile and execute significantly faster,
+    and will have more accurate type-inferencing than before
   * improvements to the win32 port (thanks to Yaroslav Kavenchuk):
     * bug fix: arguments to RUN-PROGRAM are escaped correctly
     * replace dummy implementations of CL:MACHINE-INSTANCE and 
index 27b9cc2..6109208 100644 (file)
  ("src/compiler/ir1tran")
  ("src/compiler/ir1tran-lambda")
  ("src/compiler/ir1-translators")
- ("src/compiler/ir1-step")
  ("src/compiler/ir1util")
  ("src/compiler/ir1report")
  ("src/compiler/ir1opt")
  ("src/code/sharpm"            :not-host) ; uses stuff from "code/reader"
  ("src/code/alloc"             :not-host)
 
+ ("src/code/early-step")                  ; target-thread needs *STEP-OUT*
+
  ("src/code/target-thread"     :not-host)
+
  ;; defines SB!DI:DO-DEBUG-FUN-BLOCKS, needed by target-disassem.lisp
  ("src/code/gc"     :not-host)
  ("src/code/purify" :not-host)
index 1d2e5e5..4013273 100644 (file)
@@ -711,10 +711,9 @@ like *STACK-TOP-HINT* and unsupported stuff like *TRACED-FUN-LIST*."
                ;; stepping interface
                "STEP-CONDITION" "STEP-FORM-CONDITION"
                "STEP-VALUES-CONDITION" "STEP-VARIABLE-CONDITION"
-               "STEP-CONDITION-FORM" "STEP-CONDITION-SOURCE-PATH"
-               "STEP-CONDITION-PATHNAME" "STEP-CONDITION-RESULT"
+               "STEP-CONDITION-FORM" "STEP-CONDITION-RESULT"
                "STEP-CONTINUE" "STEP-NEXT" "STEP-INTO"
-               "*STEPPER-HOOK*"
+               "STEP-CONDITION-ARGS" "*STEPPER-HOOK*" "STEP-OUT"
 
                ;; RUN-PROGRAM is not only useful for users, but also
                ;; useful to implement parts of SBCL itself, so we're
@@ -2284,7 +2283,8 @@ structure representations"
                "SINGLE-FLOAT-WIDETAG" "SINGLE-FLOAT-VALUE-SLOT"
                "SINGLE-INT-CARG-REG-SC-NUMBER"
                "SINGLE-REG-SC-NUMBER" "SINGLE-STACK-SC-NUMBER"
-               "SINGLE-STEP-BREAKPOINT-TRAP"
+               "SINGLE-STEP-AROUND-TRAP"
+               "SINGLE-STEP-BEFORE-TRAP"
                "SINGLE-VALUE-RETURN-BYTE-OFFSET" "SLOT-DOCS"
                "SLOT-LENGTH" "SLOT-NAME" "SLOT-OFFSET" "SLOT-OPTIONS"
                "SLOT-REST-P" "*STATIC-FUNS*" "STATIC-FUN-OFFSET"
index 705d945..c59ccb4 100644 (file)
@@ -1170,6 +1170,7 @@ SB-EXT:PACKAGE-LOCKED-ERROR-SYMBOL."))
 
 (define-condition step-condition ()
   ((form :initarg :form :reader step-condition-form))
+
   #!+sb-doc
   (:documentation "Common base class of single-stepping conditions.
 STEP-CONDITION-FORM holds a string representation of the form being
@@ -1180,8 +1181,18 @@ stepped."))
       "Form associated with the STEP-CONDITION.")
 
 (define-condition step-form-condition (step-condition)
-  ((source-path :initarg :source-path :reader step-condition-source-path)
-   (pathname :initarg :pathname :reader step-condition-pathname))
+  ((args :initarg :args :reader step-condition-args))
+  (:report
+   (lambda (condition stream)
+     (let ((*print-circle* t)
+           (*print-pretty* t)
+           (*print-readably* nil))
+       (format stream
+                 "Evaluating call:~%~<  ~@;~A~:>~%~
+                  ~:[With arguments:~%~{  ~S~%~}~;With unknown arguments~]~%"
+               (list (step-condition-form condition))
+               (eq (step-condition-args condition) :unknown)
+               (step-condition-args condition)))))
   #!+sb-doc
   (:documentation "Condition signalled by code compiled with
 single-stepping information when about to execute a form.
@@ -1215,14 +1226,6 @@ single-stepping information after executing a form.
 STEP-CONDITION-FORM holds the form, and STEP-CONDITION-RESULT holds
 the values returned by the form as a list. No associated restarts."))
 
-(define-condition step-variable-condition (step-result-condition)
-  ()
-  #!+sb-doc
-  (:documentation "Condition signalled by code compiled with
-single-stepping information when referencing a variable.
-STEP-CONDITION-FORM hold the symbol, and STEP-CONDITION-RESULT holds
-the value of the variable. No associated restarts."))
-
 \f
 ;;;; restart definitions
 
index 991d158..cab930c 100644 (file)
 (defconstant-eqx compiled-debug-block-nsucc-byte (byte 2 0) #'equalp)
 (def!constant compiled-debug-block-elsewhere-p #b00000100)
 
-(defconstant-eqx compiled-code-location-kind-byte (byte 3 0) #'equalp)
+(defconstant-eqx compiled-code-location-kind-byte (byte 4 0) #'equalp)
 (defparameter *compiled-code-location-kinds*
   #(:unknown-return :known-return :internal-error :non-local-exit
-    :block-start :call-site :single-value-return :non-local-entry))
+    :block-start :call-site :single-value-return :non-local-entry
+    :step-before-vop))
 \f
 ;;;; DEBUG-FUN objects
 
index accddcb..2dfd05d 100644 (file)
            str)))
 
 (defstruct (compiled-code-location
-            (:include code-location)
-            (:constructor make-known-code-location
-                          (pc debug-fun %tlf-offset %form-number
-                              %live-set kind &aux (%unknown-p nil)))
-            (:constructor make-compiled-code-location (pc debug-fun))
-            (:copier nil))
+             (:include code-location)
+             (:constructor make-known-code-location
+                           (pc debug-fun %tlf-offset %form-number
+                               %live-set kind step-info &aux (%unknown-p nil)))
+             (:constructor make-compiled-code-location (pc debug-fun))
+             (:copier nil))
   ;; an index into DEBUG-FUN's component slot
   (pc nil :type index)
   ;; a bit-vector indexed by a variable's position in
   (%live-set :unparsed :type (or simple-bit-vector (member :unparsed)))
   ;; (unexported) To see SB!C::LOCATION-KIND, do
   ;; (SB!KERNEL:TYPE-EXPAND 'SB!C::LOCATION-KIND).
-  (kind :unparsed :type (or (member :unparsed) sb!c::location-kind)))
+  (kind :unparsed :type (or (member :unparsed) sb!c::location-kind))
+  (step-info :unparsed :type (or (member :unparsed :foo) simple-string)))
 \f
 ;;;; DEBUG-SOURCEs
 
 
 ;;;; (OR X86 X86-64) support
 
-#!+(or x86 x86-64)
-(progn
-
 (defun compute-lra-data-from-pc (pc)
   (declare (type system-area-pointer pc))
   (let ((component-ptr (component-ptr-from-pc pc)))
 ;        (format t "c-lra-fpc ~A ~A ~A~%" pc code pc-offset)
          (values pc-offset code)))))
 
+#!+(or x86 x86-64)
+(progn
+
 (defconstant sb!vm::nargs-offset #.sb!vm::ecx-offset)
 
 ;;; Check for a valid return address - it could be any valid C/Lisp
@@ -1545,10 +1546,12 @@ register."
                                               (sb!c:read-var-integer blocks i)))
                               (form-number (sb!c:read-var-integer blocks i))
                               (live-set (sb!c:read-packed-bit-vector
-                                         live-set-len blocks i)))
+                                         live-set-len blocks i))
+                              (step-info (sb!c:read-var-string blocks i)))
                           (vector-push-extend (make-known-code-location
                                                pc debug-fun tlf-offset
-                                               form-number live-set kind)
+                                               form-number live-set kind
+                                               step-info)
                                               locations-buffer)
                           (setf last-pc pc))))
                      (block (make-compiled-debug-block
@@ -1866,6 +1869,8 @@ register."
                     (compiled-code-location-%live-set loc))
               (setf (compiled-code-location-kind code-location)
                     (compiled-code-location-kind loc))
+              (setf (compiled-code-location-step-info code-location)
+                    (compiled-code-location-step-info loc))
               (return-from fill-in-code-location t))))))))
 \f
 ;;;; operations on DEBUG-BLOCKs
@@ -3294,3 +3299,129 @@ register."
     ;; (There used to be more cases back before sbcl-0.7.0, when
     ;; we did special tricks to debug the IR1 interpreter.)
     ))
+
+\f
+;;;; Single-stepping
+
+;;; The single-stepper works by inserting conditional trap instructions
+;;; into the generated code (see src/compiler/*/call.lisp), currently:
+;;;
+;;;   1) Before the code generated for a function call that was
+;;;      translated to a VOP
+;;;   2) Just before the call instruction for a full call
+;;;
+;;; In both cases, the trap will only be executed if stepping has been
+;;; enabled, in which case it'll ultimately be handled by
+;;; HANDLE-SINGLE-STEP-TRAP, which will either signal a stepping condition,
+;;; or replace the function that's about to be called with a wrapper
+;;; which will signal the condition.
+
+(defun handle-single-step-trap (context-sap kind callee-register-offset)
+  (let ((context (sb!alien:sap-alien context-sap
+                                     (* os-context-t))))
+    ;; The following calls must get tail-call eliminated for
+    ;; *STEP-FRAME* to get set correctly.
+    (if (= kind single-step-before-trap)
+        (handle-single-step-before-trap context)
+        (handle-single-step-around-trap context callee-register-offset))))
+
+(defvar *step-frame* nil)
+
+(defun handle-single-step-before-trap (context)
+  (let ((step-info (single-step-info-from-context context)))
+    ;; If there was not enough debug information available, there's no
+    ;; sense in signaling the condition.
+    (when step-info
+      (let ((*step-frame* (frame-down (top-frame))))
+        ;; KLUDGE: Use the first non-foreign frame as the
+        ;; *STACK-TOP-HINT*. Getting the frame from the signal context
+        ;; would be cleaner, but SIGNAL-CONTEXT-FRAME doesn't seem
+        ;; seem to work very well currently.
+        (loop while *step-frame*
+              for dfun = (frame-debug-fun *step-frame*)
+              do (when (typep dfun 'compiled-debug-fun)
+                   (return))
+              do (setf *step-frame* (frame-down *step-frame*)))
+        (sb!impl::step-form step-info
+                            ;; We could theoretically store information in
+                            ;; the debug-info about to determine the
+                            ;; arguments here, but for now let's just pass
+                            ;; on it.
+                            :unknown)))))
+
+;;; This function will replace the fdefn / function that was in the
+;;; register at CALLEE-REGISTER-OFFSET with a wrapper function. To
+;;; ensure that the full call will use the wrapper instead of the
+;;; original, conditional trap must be emitted before the fdefn /
+;;; function is converted into a raw address.
+(defun handle-single-step-around-trap (context callee-register-offset)
+  ;; Fetch the function / fdefn we're about to call from the
+  ;; appropriate register.
+  (let* ((callee (sb!kernel::make-lisp-obj
+                  (context-register context callee-register-offset)))
+         (step-info (single-step-info-from-context context)))
+    ;; If there was not enough debug information available, there's no
+    ;; sense in signaling the condition.
+    (unless step-info
+      (return-from handle-single-step-around-trap))
+    (let* ((fun (lambda (&rest args)
+                  (flet ((call ()
+                           (apply (typecase callee
+                                    (fdefn (fdefn-fun callee))
+                                    (function callee))
+                                  args)))
+                    (let ((sb!impl::*step-out* :maybe))
+                      (unwind-protect
+                           ;; Signal a step condition
+                           (let* ((step-in
+                                   (let ((*step-frame* (frame-down (top-frame))))
+                                     (sb!impl::step-form step-info args))))
+                             ;; And proceed based on its return value.
+                             (if step-in
+                                 ;; If STEP-INTO was selected we pass
+                                 ;; the return values to STEP-VALUES which
+                                 ;; will show the return value.
+                                 (multiple-value-call #'sb!impl::step-values
+                                   step-info
+                                   (call))
+                                 ;; If STEP-NEXT or STEP-CONTINUE was
+                                 ;; selected we disable the stepper for
+                                 ;; the duration of the call.
+                                 (sb!impl::with-stepping-disabled
+                                   (call))))
+                        ;; If the use selected the STEP-OUT restart
+                        ;; somewhere during the call, resume stepping
+                        (when (eq sb!impl::*step-out* t)
+                          (sb!impl::enable-stepping)))))))
+           (new-callee (etypecase callee
+                         (fdefn
+                          (let ((fdefn (make-fdefn (gensym))))
+                            (setf (fdefn-fun fdefn) fun)
+                            fdefn))
+                         (function fun))))
+      ;; And then store the wrapper in the same place.
+      (setf (context-register context callee-register-offset)
+            (get-lisp-obj-address new-callee)))))
+
+;;; Given a signal context, fetch the step-info that's been stored in
+;;; the debug info at the trap point.
+(defun single-step-info-from-context (context)
+  (multiple-value-bind (pc-offset code)
+      (compute-lra-data-from-pc (context-pc context))
+    (let* ((debug-fun (debug-fun-from-pc code pc-offset))
+           (location (code-location-from-pc debug-fun
+                                            pc-offset
+                                            nil)))
+      (handler-case
+          (progn
+            (fill-in-code-location location)
+            (code-location-debug-source location)
+            (compiled-code-location-step-info location))
+        (debug-condition ()
+          nil)))))
+
+;;; Return the frame that triggered a single-step condition. Used to
+;;; provide a *STACK-TOP-HINT*.
+(defun find-stepped-frame ()
+  (or *step-frame*
+      (top-frame)))
index 08ce07d..f2c10c1 100644 (file)
@@ -70,7 +70,9 @@
   (once-only ((len `(read-var-integer ,vec ,index)))
     (once-only ((res `(make-string ,len)))
       `(progn
-         (%byte-blt ,vec ,index ,res 0 ,len)
+         (loop for i from 0 below ,len
+               do (setf (aref ,res i)
+                        (code-char (aref ,vec (+ ,index i)))))
          (incf ,index ,len)
          ,res))))
 
index cad19af..bb78629 100644 (file)
@@ -120,9 +120,14 @@ Inspecting frames:
   SOURCE [n]     displays frame's source form with n levels of enclosing forms.
 
 Stepping:
-  STEP  Selects the CONTINUE restart if one exists and starts
+  START Selects the CONTINUE restart if one exists and starts
         single-stepping. Single stepping affects only code compiled with
         under high DEBUG optimization quality. See User Manual for details.
+  STEP  Steps into the current form.
+  NEXT  Steps over the current form.
+  OUT   Stops stepping temporarily, but resumes it when the topmost frame that
+        was stepped into returns.
+  STOP  Stops single-stepping.
 
 Function and macro commands:
  (SB-DEBUG:ARG n)
@@ -512,7 +517,6 @@ reset to ~S."
   (terpri stream))
 
 (defun %invoke-debugger (condition)
-
   (let ((*debug-condition* condition)
         (*debug-restarts* (compute-restarts condition))
         (*nested-debug-condition* nil))
@@ -522,7 +526,8 @@ reset to ~S."
         ;; when people redirect *ERROR-OUTPUT*, they could reasonably
         ;; expect to see error messages logged there, regardless of what
         ;; the debugger does afterwards.)
-        (%print-debugger-invocation-reason condition *error-output*)
+        (unless (typep condition 'step-condition)
+          (%print-debugger-invocation-reason condition *error-output*))
       (error (condition)
         (setf *nested-debug-condition* condition)
         (let ((ndc-type (type-of *nested-debug-condition*)))
@@ -697,6 +702,11 @@ reset to ~S."
 (defvar *debug-loop-fun* #'debug-loop-fun
   "a function taking no parameters that starts the low-level debug loop")
 
+;;; When the debugger is invoked due to a stepper condition, we don't
+;;; want to print the current frame before the first prompt for aesthetic
+;;; reasons.
+(defvar *suppress-frame-print* nil)
+
 ;;; This calls DEBUG-LOOP, performing some simple initializations
 ;;; before doing so. INVOKE-DEBUGGER calls this to actually get into
 ;;; the debugger. SB!KERNEL::ERROR-ERROR calls this in emergencies
@@ -708,7 +718,8 @@ reset to ~S."
         (*read-suppress* nil))
     (unless (typep *debug-condition* 'step-condition)
       (clear-input *debug-io*))
-    (funcall *debug-loop-fun*)))
+    (let ((*suppress-frame-print* (typep *debug-condition* 'step-condition)))
+      (funcall *debug-loop-fun*))))
 \f
 ;;;; DEBUG-LOOP
 
@@ -738,8 +749,11 @@ reset to ~S."
                       (princ condition *debug-io*)
                       (/show0 "handling d-c by THROWing DEBUG-LOOP-CATCHER")
                       (throw 'debug-loop-catcher nil))))
-      (terpri *debug-io*)
-      (print-frame-call *current-frame* *debug-io* :verbosity 2)
+      (cond (*suppress-frame-print*
+             (setf *suppress-frame-print* nil))
+            (t
+             (terpri *debug-io*)
+             (print-frame-call *current-frame* *debug-io* :verbosity 2)))
       (loop
        (catch 'debug-loop-catcher
          (handler-bind ((error (lambda (condition)
@@ -1311,15 +1325,41 @@ reset to ~S."
                                  (svref translations form-num)
                                  context))))
 \f
-;;; step to the next steppable form
-(!def-debug-command "STEP" ()
-  (let ((restart (find-restart 'continue *debug-condition*)))
-    (cond (restart
-           (setf *stepping* t
-                 *step* t)
+
+;;; start single-stepping
+(!def-debug-command "START" ()
+  (if (typep *debug-condition* 'step-condition)
+      (format *debug-io* "~&Already single-stepping.~%")
+      (let ((restart (find-restart 'continue *debug-condition*)))
+        (cond (restart
+               (sb!impl::enable-stepping)
+               (invoke-restart restart))
+              (t
+               (format *debug-io* "~&Non-continuable error, cannot start stepping.~%"))))))
+
+(defmacro def-step-command (command-name restart-name)
+  `(!def-debug-command ,command-name ()
+     (if (typep *debug-condition* 'step-condition)
+         (let ((restart (find-restart ',restart-name *debug-condition*)))
+           (aver restart)
            (invoke-restart restart))
-          (t
-           (format *debug-io* "~&Non-continuable error, cannot step.~%")))))
+         (format *debug-io* "~&Not currently single-stepping. (Use START to activate the single-stepper)~%"))))
+
+(def-step-command "STEP" step-into)
+(def-step-command "NEXT" step-next)
+(def-step-command "STOP" step-continue)
+
+(!def-debug-command-alias "S" "STEP")
+(!def-debug-command-alias "N" "NEXT")
+
+(!def-debug-command "OUT" ()
+  (if (typep *debug-condition* 'step-condition)
+      (if sb!impl::*step-out*
+          (let ((restart (find-restart 'step-out *debug-condition*)))
+            (aver restart)
+            (invoke-restart restart))
+          (format *debug-io* "~&OUT can only be used step out of frames that were originally stepped into with STEP.~%"))
+      (format *debug-io* "~&Not currently single-stepping. (Use START to activate the single-stepper)~%")))
 
 ;;; miscellaneous commands
 
diff --git a/src/code/early-step.lisp b/src/code/early-step.lisp
new file mode 100644 (file)
index 0000000..53a42ba
--- /dev/null
@@ -0,0 +1,63 @@
+;;;; single stepper for SBCL
+
+;;;; This software is part of the SBCL system. See the README file for
+;;;; more information.
+;;;;
+;;;; This software is derived from the CMU CL system, which was
+;;;; written at Carnegie Mellon University and released into the
+;;;; public domain. The software is in the public domain and is
+;;;; provided with absolutely no warranty. See the COPYING and CREDITS
+;;;; files for more information.
+
+;;;; Single stepping works by having compiler insert STEP-CONDITION
+;;;; signalling forms into code compiled at high debug settings, and
+;;;; having a handler for them at the toplevel.
+
+(in-package "SB!IMPL")
+
+;; Used for controlling whether the stepper is enabled / disabled when
+;; building without SB-THREAD. With SB-THREAD, a slot in the thread
+;; structure is used instead. (See EMIT-SINGLE-STEP-TEST in
+;; src/compiler/x86/call.lisp).
+#!-sb-thread
+(defvar *stepping* nil)
+
+;; Used for implementing the STEP-OUT restart. The step-wrapper will
+;; bind this to :MAYBE, before calling the wrapped code. When
+;; unwinding, the wrapper will check whether it's been set to T. If
+;; so, it'll re-enable the stepper. This is a tri-state variable (NIL,
+;; :MAYBE, T) so that the debugger can detect in advance whether the
+;; OUT debugger command will actually have a wrapper to step out to.
+(defvar *step-out* nil)
+
+(symbol-macrolet ((place
+                   #!+sb-thread (sb!thread::thread-stepping)
+                   #!-sb-thread *stepping*))
+  (defun (setf stepping) (new-value)
+    (setf place new-value))
+  (defun stepping-enabled-p ()
+    place))
+
+(defun enable-stepping ()
+  (setf (stepping) t))
+(defun disable-stepping ()
+  (setf (stepping) nil))
+
+
+(defmacro with-stepping-enabled (&body body)
+  (let ((orig (gensym)))
+    `(let ((,orig (stepping-enabled-p)))
+       (unwind-protect
+            (progn
+              (enable-stepping)
+              ,@body)
+         (setf (stepping) ,orig)))))
+
+(defmacro with-stepping-disabled (&body body)
+  (let ((orig (gensym)))
+    `(let ((,orig (stepping-enabled-p)))
+       (unwind-protect
+            (progn
+              (disable-stepping)
+              ,@body)
+         (setf (stepping) ,orig)))))
index a7222b3..314d10c 100644 (file)
 
 (in-package "SB-IMPL") ; in warm SBCL
 
+(defun step-form (form args)
+  (restart-case
+      (signal 'step-form-condition
+              :form form
+              :args args)
+    (step-continue ()
+      :report "Resume normal execution"
+      (disable-stepping)
+      (setf *step-out* nil))
+    (step-out ()
+      :report "Resume stepping after returning from this function"
+      (disable-stepping)
+      (setf *step-out* t)
+      nil)
+    (step-next ()
+      :report "Step over call"
+      nil)
+    (step-into ()
+      :report "Step into call"
+      t)))
+
+(defun step-values (form &rest values)
+  (declare (dynamic-extent values))
+  (signal 'step-values-condition :form form :result values)
+  (values-list values))
+
 (defvar *step-help* "The following commands are available at the single
 stepper's prompt:
 
@@ -28,11 +54,6 @@ stepper's prompt:
 
 (defgeneric single-step (condition))
 
-(defmethod single-step ((condition step-variable-condition))
-  (format *debug-io* "; ~A => ~S~%"
-          (step-condition-form condition)
-          (step-condition-result condition)))
-
 (defmethod single-step ((condition step-values-condition))
   (let ((values (step-condition-result condition)))
     (format *debug-io* "; ~A => ~:[#<no value>~;~{~S~^, ~}~]~%"
@@ -40,25 +61,20 @@ stepper's prompt:
             values values)))
 
 (defmethod single-step ((condition step-form-condition))
-  (let ((form (step-condition-form condition)))
-    (loop
-     (format *debug-io* "; form ~A~%STEP] " form)
-     (finish-output *debug-io*)
-     (let ((line (read-line *debug-io*)))
-       (if (plusp (length line))
-           (case (char-upcase (schar line 0))
-             (#\B
-              (backtrace))
-             (#\Q
-              (abort condition))
-             (#\C
-              (step-continue condition))
-             (#\N
-              (step-next condition))
-             (#\S
-              (step-into condition))
-             (#\?
-              (write-line *step-help* *debug-io*))))))))
+  (let ((form (step-condition-form condition))
+        (args (step-condition-args condition)))
+    (let ((*print-circle* t)
+          (*print-pretty* t)
+          (*print-readably* nil))
+      (format *debug-io*
+              "; Evaluating call:~%~<; ~@;  ~A~:>~%~
+               ; ~:[With arguments:~%~<; ~@;~{  ~S~^~%~}~:>~;With unknown arguments~]~%"
+              (list form)
+              (eq args :unknown)
+              (list args)))
+    (finish-output *debug-io*)
+    (let ((*stack-top-hint* (sb-di::find-stepped-frame)))
+      (invoke-debugger condition))))
 
 (defvar *stepper-hook* 'single-step
   #+sb-doc "Customization hook for alternative single-steppers.
@@ -66,7 +82,8 @@ stepper's prompt:
 with the STEP-CONDITION as argument.")
 
 (defun invoke-stepper (condition)
-  (when (and *stepping* *stepper-hook*)
+  (when (and (stepping-enabled-p)
+             *stepper-hook*)
     (let ((hook *stepper-hook*)
           (*stepper-hook* nil))
       (funcall hook condition))))
@@ -77,9 +94,10 @@ with the STEP-CONDITION as argument.")
 outside the lexical scope of the form can be stepped into only if the
 functions in question have been compiled with sufficient DEBUG policy
 to be at least partially steppable."
-  `(let ((*stepping* t)
-         (*step* t))
-    (declare (optimize (sb-c:insert-step-conditions 0)))
-    (format t "Single stepping. Type ? for help.~%")
-    (locally (declare (optimize (sb-c:insert-step-conditions 3)))
-      ,form)))
+  `(locally
+       (declare (optimize (sb-c:insert-step-conditions 0)))
+     (format t "Single stepping. Type ? for help.~%")
+     (let ((*step-out* :maybe))
+       (with-stepping-enabled
+         (locally (declare (optimize (sb-c:insert-step-conditions 3)))
+           ,form)))))
index 9507b50..302a9fa 100644 (file)
@@ -156,13 +156,18 @@ in future versions."
         int (word unsigned-long) (n unsigned-long))))
 
 ;;; used by debug-int.lisp to access interrupt contexts
-#!-(and sb-fluid sb-thread) (declaim (inline sb!vm::current-thread-offset-sap))
+#!-(or sb-fluid sb-thread) (declaim (inline sb!vm::current-thread-offset-sap))
 #!-sb-thread
 (defun sb!vm::current-thread-offset-sap (n)
   (declare (type (unsigned-byte 27) n))
   (sap-ref-sap (alien-sap (extern-alien "all_threads" (* t)))
                (* n sb!vm:n-word-bytes)))
 
+#!+sb-thread
+(defun sb!vm::current-thread-offset-sap (n)
+  (declare (type (unsigned-byte 27) n))
+  (sb!vm::current-thread-offset-sap n))
+
 ;;;; spinlocks
 
 (declaim (inline get-spinlock release-spinlock))
@@ -607,6 +612,7 @@ returns the thread exits."
                   (sb!kernel::*restart-clusters* nil)
                   (sb!kernel::*handler-clusters* nil)
                   (sb!kernel::*condition-restarts* nil)
+                  (sb!impl::*step-out* nil)
                   ;; internal printer variables
                   (sb!impl::*previous-case* nil)
                   (sb!impl::*previous-readtable-case* nil)
@@ -757,3 +763,15 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated"
 
 (defun sb!vm::locked-symbol-global-value-add (symbol-name delta)
   (sb!vm::locked-symbol-global-value-add symbol-name delta))
+
+;;; Stepping
+
+(defun thread-stepping ()
+  (sb!kernel:make-lisp-obj
+   (sap-ref-word (current-thread-sap)
+                 (* sb!vm::thread-stepping-slot sb!vm:n-word-bytes))))
+
+(defun (setf thread-stepping) (value)
+  (setf (sap-ref-word (current-thread-sap)
+                      (* sb!vm::thread-stepping-slot sb!vm:n-word-bytes))
+        (sb!kernel:get-lisp-obj-address value)))
index 17ff6e8..4aea10d 100644 (file)
@@ -65,17 +65,6 @@ designator or a stream for the default userinit file, or NIL. If the function
 returns NIL, no userinit file is used unless one has been specified on the
 command-line.")
 
-;;;; stepping control
-(defvar *step*)
-(defvar *stepping*)
-(defvar *step-form-stack* nil
-  #!+sb-doc
-  "A place for single steppers to push information about
-STEP-FORM-CONDITIONS avaiting the corresponding
-STEP-VALUES-CONDITIONS. The system is guaranteed to empty the stack
-when stepping terminates, so that it remains in sync, but doesn't
-modify it in any other way: it is provided for implmentors of single
-steppers to maintain contextual information.")
 \f
 ;;;; miscellaneous utilities for working with with TOPLEVEL
 
@@ -581,24 +570,22 @@ that provides the REPL for the system. Assumes that *STANDARD-INPUT* and
       ;; most CL specials (most critically *PACKAGE*).
       (with-rebound-io-syntax
           (handler-bind ((step-condition 'invoke-stepper))
-            (let ((*stepping* nil)
-                  (*step* nil))
-              (loop
+            (loop
                (/show0 "about to set up restarts in TOPLEVEL-REPL")
-                 ;; CLHS recommends that there should always be an
-                 ;; ABORT restart; we have this one here, and one per
-                 ;; debugger level.
-                 (with-simple-restart
-                     (abort "~@<Exit debugger, returning to top level.~@:>")
-                   (catch 'toplevel-catcher
-                     #!-win32 (sb!unix::reset-signal-mask)
-                     ;; In the event of a control-stack-exhausted-error, we
-                     ;; should have unwound enough stack by the time we get
-                     ;; here that this is now possible.
-                     #!-win32
-                     (sb!kernel::protect-control-stack-guard-page 1)
-                     (funcall repl-fun noprint)
-                     (critically-unreachable "after REPL"))))))))))
+               ;; CLHS recommends that there should always be an
+               ;; ABORT restart; we have this one here, and one per
+               ;; debugger level.
+               (with-simple-restart
+                   (abort "~@<Exit debugger, returning to top level.~@:>")
+                 (catch 'toplevel-catcher
+                   #!-win32 (sb!unix::reset-signal-mask)
+                   ;; In the event of a control-stack-exhausted-error, we
+                   ;; should have unwound enough stack by the time we get
+                   ;; here that this is now possible.
+                   #!-win32
+                   (sb!kernel::protect-control-stack-guard-page 1)
+                   (funcall repl-fun noprint)
+                   (critically-unreachable "after REPL")))))))))
 
 ;;; Our default REPL prompt is the minimal traditional one.
 (defun repl-prompt-fun (stream)
@@ -642,8 +629,7 @@ that provides the REPL for the system. Assumes that *STANDARD-INPUT* and
                 (fresh-line)
                 (prin1 result)))))
      ;; If we started stepping in the debugger we want to stop now.
-     (setf *stepping* nil
-           *step* nil))))
+     (disable-stepping))))
 \f
 ;;; a convenient way to get into the assembly-level debugger
 (defun %halt ()
index d4db1bc..d9ccfee 100644 (file)
@@ -623,11 +623,15 @@ default-value-8
      (:vop-var vop)
      (:info ,@(unless (or variable (eq return :tail)) '(arg-locs))
             ,@(unless variable '(nargs))
-            ,@(when (eq return :fixed) '(nvals)))
+            ,@(when (eq return :fixed) '(nvals))
+            step-instrumenting)
 
      (:ignore #!+gengc ,@(unless (eq return :tail) '(return-pc-pass))
               ,@(unless (or variable (eq return :tail)) '(arg-locs))
-              ,@(unless variable '(args)))
+              ,@(unless variable '(args))
+              ;; Step instrumentation for full calls not implemented yet.
+              ;; See the PPC backend for an example.
+              step-instrumenting)
 
      (:temporary (:sc descriptor-reg
                   :offset ocfp-offset
@@ -1231,3 +1235,12 @@ default-value-8
   (frob unknown-key-arg-error unknown-key-arg-error
     sb!c::%unknown-key-arg-error key)
   (frob nil-fun-returned-error nil-fun-returned-error nil fun))
+
+;;; Single-stepping
+
+(define-vop (step-instrument-before-vop)
+  (:policy :fast-safe)
+  (:vop-var vop)
+  (:generator 3
+    ;; Stub! See the PPC backend for an example.
+    (note-this-location vop :step-before-vop)))
index 2224bd2..5254a2b 100644 (file)
   cerror
   breakpoint
   fun-end-breakpoint
-  single-step-breakpoint)
+  single-step-breakpoint
+  ;; Stepper actually not implemented on Alpha, but these constants
+  ;; are still needed to avoid undefined variable warnings during sbcl
+  ;; build.
+  single-step-around
+  single-step-before)
 
 (defenum (:prefix trace-table-)
   normal
index 797114a..a286675 100644 (file)
@@ -20,7 +20,8 @@
 
 (deftype location-kind ()
   '(member :unknown-return :known-return :internal-error :non-local-exit
-           :block-start :call-site :single-value-return :non-local-entry))
+           :block-start :call-site :single-value-return :non-local-entry
+           :step-before-vop))
 
 ;;; The LOCATION-INFO structure holds the information what we need
 ;;; about locations which code generation decided were "interesting".
@@ -89,7 +90,7 @@
 ;;; are spilled.
 (defun dump-1-location (node block kind tlf-num label live var-locs vop)
   (declare (type node node) (type ir2-block block)
-           (type local-tn-bit-vector live)
+           (type (or null local-tn-bit-vector) live)
            (type (or label index) label)
            (type location-kind kind) (type (or index null) tlf-num)
            (type hash-table var-locs) (type (or vop null) vop))
       (write-var-integer (source-path-tlf-number path) *byte-buffer*))
     (write-var-integer (source-path-form-number path) *byte-buffer*))
 
-  (write-packed-bit-vector (compute-live-vars live node block var-locs vop)
-                           *byte-buffer*)
-
+  (if live
+      (write-packed-bit-vector (compute-live-vars live node block var-locs vop)
+                               *byte-buffer*)
+      (write-packed-bit-vector
+       (make-array (logandc2 (+ (hash-table-count var-locs) 7) 7)
+                   :initial-element 0
+                   :element-type 'bit)
+       *byte-buffer*))
+
+  (write-var-string (or (and (typep node 'combination)
+                             (combination-step-info node))
+                        "")
+                    *byte-buffer*)
   (values))
 
 ;;; Extract context info from a Location-Info structure and use it to
index b013c6a..2428126 100644 (file)
   #!+(or x86 x86-64) (pseudo-atomic-bits)
   (interrupt-data :c-type "struct interrupt_data *"
                   :length #!+alpha 2 #!-alpha 1)
+  (stepping)
   (interrupt-contexts :c-type "os_context_t *" :rest-p t))
index ac0043e..c41f8a3 100644 (file)
@@ -21,6 +21,7 @@
     sb!kernel::undefined-alien-function-error
     sb!kernel::memory-fault-error
     sb!di::handle-breakpoint
+    sb!di::handle-single-step-trap
     fdefinition-object
     #!+sb-thread sb!thread::run-interruption
     #!+win32 sb!kernel::handle-win32-exception))
@@ -54,6 +55,8 @@
     sb!unix::*interrupt-pending*
     *gc-inhibit*
     *gc-pending*
+    #!-sb-thread
+    *stepping*
 
     ;; hash table weaknesses
     :key
index 783ba7b..81588fb 100644 (file)
     (ir1-error-bailout (start next result form)
       (let ((*current-path* (or (gethash form *source-paths*)
                                 (cons form *current-path*))))
-        (cond ((step-form-p form)
-               (ir1-convert-step start next result form))
-              ((atom form)
+        (cond ((atom form)
                (cond ((and (symbolp form) (not (keywordp form)))
                       (ir1-convert-var start next result form))
                      ((leaf-p form)
 \f
 ;;;; converting combinations
 
+;;; Does this form look like something that we should add single-stepping
+;;; instrumentation for?
+(defun step-form-p (form)
+  (flet ((step-symbol-p (symbol)
+           (not (member (symbol-package symbol)
+                        (load-time-value
+                         ;; KLUDGE: packages we're not interested in
+                         ;; stepping.
+                         (mapcar #'find-package '(sb!c sb!int sb!impl
+                                                  sb!kernel sb!pcl)))))))
+    (and *allow-instrumenting*
+         (policy *lexenv* (= insert-step-conditions 3))
+         (listp form)
+         (symbolp (car form))
+         (step-symbol-p (car form)))))
+
 ;;; Convert a function call where the function FUN is a LEAF. FORM is
 ;;; the source for the call. We return the COMBINATION node so that
 ;;; the caller can poke at it if it wants to.
   (let ((ctran (make-ctran))
         (fun-lvar (make-lvar)))
     (ir1-convert start ctran fun-lvar `(the (or function symbol) ,fun))
-    (ir1-convert-combination-args fun-lvar ctran next result (cdr form))))
+    (let ((combination
+           (ir1-convert-combination-args fun-lvar ctran next result (cdr form))))
+      (when (step-form-p form)
+        ;; Store a string representation of the form in the
+        ;; combination node. This will let the IR2 translator know
+        ;; that we want stepper instrumentation for this node. The
+        ;; string will be stored in the debug-info by DUMP-1-LOCATION.
+        (setf (combination-step-info combination)
+              (let ((*print-pretty* t)
+                    (*print-circle* t)
+                    (*print-readably* nil))
+                (prin1-to-string form))))
+      combination)))
 
 ;;; Convert the arguments to a call and make the COMBINATION
 ;;; node. FUN-LVAR yields the function to call. ARGS is the list of
index 2963865..30a0cff 100644 (file)
     (vop move node block x y))
   (values))
 
+;;; Determine whether we should emit a single-stepper breakpoint
+;;; around a call / before a vop.
+(defun emit-step-p (node)
+  (if (and (policy node (> insert-step-conditions 1))
+           (typep node 'combination))
+      (combination-step-info node)
+      nil))
+
 ;;; If there is any CHECK-xxx template for TYPE, then return it,
 ;;; otherwise return NIL.
 (defun type-check-template (type)
             (when (and lvar (lvar-dynamic-extent lvar))
               (vop current-stack-pointer call block
                    (ir2-lvar-stack-pointer (lvar-info lvar))))
+            (when (emit-step-p call)
+              (vop sb!vm::step-instrument-before-vop call block))
             (if info-args
                 (emit-template call block template args r-refs info-args)
                 (emit-template call block template args r-refs))
           (vop* tail-call-named node block
                 (fun-tn old-fp return-pc pass-refs)
                 (nil)
-                nargs)
+                nargs
+                (emit-step-p node))
           (vop* tail-call node block
                 (fun-tn old-fp return-pc pass-refs)
                 (nil)
-                nargs))))
+                nargs
+                (emit-step-p node)))))
 
   (values))
 
           (fun-lvar-tn node block (basic-combination-fun node))
         (if named
             (vop* call-named node block (fp fun-tn args) (loc-refs)
-                  arg-locs nargs nvals)
+                  arg-locs nargs nvals (emit-step-p node))
             (vop* call node block (fp fun-tn args) (loc-refs)
-                  arg-locs nargs nvals))
+                  arg-locs nargs nvals (emit-step-p node)))
         (move-lvar-result node block locs lvar))))
   (values))
 
           (fun-lvar-tn node block (basic-combination-fun node))
         (if named
             (vop* multiple-call-named node block (fp fun-tn args) (loc-refs)
-                  arg-locs nargs)
+                  arg-locs nargs (emit-step-p node))
             (vop* multiple-call node block (fp fun-tn args) (loc-refs)
-                  arg-locs nargs)))))
+                  arg-locs nargs (emit-step-p node))))))
   (values))
 
 ;;; stuff to check in PONDER-FULL-CALL
        ((and 2lvar
              (eq (ir2-lvar-kind 2lvar) :unknown))
         (vop* multiple-call-variable node block (start fun nil)
-              ((reference-tn-list (ir2-lvar-locs 2lvar) t))))
+              ((reference-tn-list (ir2-lvar-locs 2lvar) t))
+              (emit-step-p node)))
        (t
         (let ((locs (standard-result-tns lvar)))
           (vop* call-variable node block (start fun nil)
-                ((reference-tn-list locs t)) (length locs))
+                ((reference-tn-list locs t)) (length locs)
+                (emit-step-p node))
           (move-lvar-result node block locs lvar)))))))
 
 ;;; Reset the stack pointer to the start of the specified
index 8035520..439541f 100644 (file)
@@ -652,10 +652,14 @@ default-value-8
      (:vop-var vop)
      (:info ,@(unless (or variable (eq return :tail)) '(arg-locs))
             ,@(unless variable '(nargs))
-            ,@(when (eq return :fixed) '(nvals)))
+            ,@(when (eq return :fixed) '(nvals))
+            step-instrumenting)
 
      (:ignore ,@(unless (or variable (eq return :tail)) '(arg-locs))
-              ,@(unless variable '(args)))
+              ,@(unless variable '(args))
+              ;; Step instrumentation for full calls not implemented yet.
+              ;; See the PPC backend for an example.
+              step-instrumenting)
 
      (:temporary (:sc descriptor-reg
                   :offset ocfp-offset
@@ -1249,3 +1253,12 @@ default-value-8
   (frob unknown-key-arg-error unknown-key-arg-error
     sb!c::%unknown-key-arg-error key)
   (frob nil-fun-returned-error nil-fun-returned-error nil fun))
+
+;;; Single-stepping
+
+(define-vop (step-instrument-before-vop)
+  (:policy :fast-safe)
+  (:vop-var vop)
+  (:generator 3
+    ;; Stub! See the PPC backend for an example.
+    (note-this-location vop :step-before-vop)))
index 8b3f615..c133443 100644 (file)
   unused
   pseudo-atomic
   object-not-list
-  object-not-instance)
+  object-not-instance
+  ;; Stepper actually not implemented on Mips, but these constants
+  ;; are still needed to avoid undefined variable warnings during sbcl
+  ;; build.
+  single-step-around
+  single-step-before)
 
 (defenum (:prefix trace-table-)
   normal
index de4ecf7..6319c5b 100644 (file)
   ;; if a call to a known global function, contains the FUN-INFO.
   (fun-info nil :type (or fun-info null))
   ;; some kind of information attached to this node by the back end
-  (info nil))
+  (info nil)
+  (step-info))
 
 ;;; The COMBINATION node represents all normal function calls,
 ;;; including FUNCALL. This is distinct from BASIC-COMBINATION so that
index a67ea18..038a510 100644 (file)
@@ -621,7 +621,8 @@ default-value-8
      (:vop-var vop)
      (:info ,@(unless (or variable (eq return :tail)) '(arg-locs))
             ,@(unless variable '(nargs))
-            ,@(when (eq return :fixed) '(nvals)))
+            ,@(when (eq return :fixed) '(nvals))
+            step-instrumenting)
 
      (:ignore
       ,@(unless (or variable (eq return :tail)) '(arg-locs))
@@ -665,6 +666,8 @@ default-value-8
      ,@(when (eq return :fixed)
          '((:temporary (:scs (descriptor-reg) :from :eval) move-temp)))
 
+     (:temporary (:scs (descriptor-reg) :to :eval) stepping)
+
      ,@(unless (eq return :tail)
          '((:temporary (:scs (non-descriptor-reg)) temp)
            (:temporary (:sc control-stack :offset nfp-save-offset) nfp-save)))
@@ -677,9 +680,11 @@ default-value-8
                      15
                      (if (eq return :unknown) 25 0))
        (trace-table-entry trace-table-call-site)
+
        (let* ((cur-nfp (current-nfp-tn vop))
               ,@(unless (eq return :tail)
                   '((lra-label (gen-label))))
+              (step-done-label (gen-label))
               (filler
                (remove nil
                        (list :load-nargs
@@ -741,7 +746,30 @@ default-value-8
                                     '(if (> nargs register-arg-count)
                                          (move cfp-tn new-fp)
                                          (move cfp-tn csp-tn))))))
-                      ((nil))))))
+                      ((nil)))))
+                (insert-step-instrumenting (callable-tn)
+                  ;; Conditionally insert a conditional trap:
+                  (when step-instrumenting
+                     ;; Get the symbol-value of SB!IMPL::*STEPPING*
+                    (loadw stepping
+                           null-tn
+                           (+ symbol-value-slot
+                              (truncate (static-symbol-offset 'sb!impl::*stepping*)
+                                        n-word-bytes))
+                           other-pointer-lowtag)
+                    (inst cmpw stepping null-tn)
+                    ;; If it's not null, trap.
+                    (inst beq step-done-label)
+                    ;; CONTEXT-PC will be pointing here when the
+                    ;; interrupt is handled, not after the UNIMP.
+                    (note-this-location vop :step-before-vop)
+                    ;; Construct a trap code with the low bits from
+                    ;; SINGLE-STEP-AROUND-TRAP and the high bits from
+                    ;; the register number of CALLABLE-TN.
+                    (inst unimp (logior single-step-around-trap
+                                        (ash (reg-tn-encoding callable-tn)
+                                             5)))
+                    (emit-label step-done-label))))
            ,@(if named
                  `((sc-case name
                      (descriptor-reg (move name-pass name))
@@ -752,6 +780,10 @@ default-value-8
                       (loadw name-pass code-tn (tn-offset name)
                              other-pointer-lowtag)
                       (do-next-filler)))
+                   ;; The step instrumenting must be done after
+                   ;; FUNCTION is loaded, but before ENTRY-POINT is
+                   ;; calculated.
+                   (insert-step-instrumenting name-pass)
                    (loadw entry-point name-pass fdefn-raw-addr-slot
                           other-pointer-lowtag)
                    (do-next-filler))
@@ -767,6 +799,10 @@ default-value-8
                    (loadw function lexenv closure-fun-slot
                     fun-pointer-lowtag)
                    (do-next-filler)
+                   ;; The step instrumenting must be done before
+                   ;; after FUNCTION is loaded, but before ENTRY-POINT
+                   ;; is calculated.
+                   (insert-step-instrumenting function)
                    (inst addi entry-point function
                     (- (ash simple-fun-code-offset word-shift)
                      fun-pointer-lowtag))
@@ -1198,3 +1234,26 @@ default-value-8
   (frob unknown-key-arg-error unknown-key-arg-error
         sb!c::%unknown-key-arg-error key)
   (frob nil-fun-returned-error nil-fun-returned-error nil fun))
+
+(define-vop (step-instrument-before-vop)
+  (:temporary (:scs (descriptor-reg)) stepping)
+  (:policy :fast-safe)
+  (:vop-var vop)
+  (:generator 3
+    ;; Get the symbol-value of SB!IMPL::*STEPPING*
+    (loadw stepping
+           null-tn
+           (+ symbol-value-slot
+              (truncate (static-symbol-offset 'sb!impl::*stepping*)
+                        n-word-bytes))
+           other-pointer-lowtag)
+    (inst cmpw stepping null-tn)
+    ;; If it's not null, trap.
+    (inst beq DONE)
+    ;; CONTEXT-PC will be pointing here when the interrupt is handled,
+    ;; not after the UNIMP.
+    (note-this-location vop :step-before-vop)
+    ;; CALLEE-REGISTER-OFFSET isn't needed for before-traps, so we
+    ;; can just use a bare SINGLE-STEP-BEFORE-TRAP as the code.
+    (inst unimp single-step-before-trap)
+    DONE))
index eddee91..0686461 100644 (file)
   breakpoint
   fun-end-breakpoint
   after-breakpoint
-  fixnum-additive-overflow)
+  fixnum-additive-overflow
+  single-step-around
+  single-step-before)
 
-(defenum (:prefix object-not- :suffix -trap :start 16)
+(defenum (:prefix object-not- :suffix -trap :start 24)
   list
   instance)
 
index b3b4abb..428f6dd 100644 (file)
@@ -619,11 +619,15 @@ default-value-8
      (:vop-var vop)
      (:info ,@(unless (or variable (eq return :tail)) '(arg-locs))
             ,@(unless variable '(nargs))
-            ,@(when (eq return :fixed) '(nvals)))
+            ,@(when (eq return :fixed) '(nvals))
+            step-instrumenting)
 
      (:ignore
       ,@(unless (or variable (eq return :tail)) '(arg-locs))
-      ,@(unless variable '(args)))
+      ,@(unless variable '(args))
+      ;; Step instrumentation for full calls not implemented yet.
+      ;; See the PPC backend for an example.
+      step-instrumenting)
 
      (:temporary (:sc descriptor-reg
                   :offset ocfp-offset
@@ -1200,3 +1204,12 @@ default-value-8
   (frob unknown-key-arg-error unknown-key-arg-error
     sb!c::%unknown-key-arg-error key)
   (frob nil-fun-returned-error nil-fun-returned-error nil fun))
+
+;;; Single-stepping
+
+(define-vop (step-instrument-before-vop)
+  (:policy :fast-safe)
+  (:vop-var vop)
+  (:generator 3
+    ;; Stub! See the PPC backend for an example.
+    (note-this-location vop :step-before-vop)))
index 2dc5c35..792612e 100644 (file)
   cerror
   breakpoint
   fun-end-breakpoint
-  after-breakpoint)
-
-(defenum (:prefix object-not- :suffix -trap :start 16)
+  after-breakpoint
+  ;; Stepper actually not implemented on Sparc, but these constants
+  ;; are still needed to avoid undefined variable warnings during sbcl
+  ;; build.
+  single-step-around
+  single-step-before)
+
+(defenum (:prefix object-not- :suffix -trap :start 24)
   list
   instance)
 
index 94dab21..e0af725 100644 (file)
                (:info
                ,@(unless (or variable (eq return :tail)) '(arg-locs))
                ,@(unless variable '(nargs))
-               ,@(when (eq return :fixed) '(nvals)))
+               ,@(when (eq return :fixed) '(nvals))
+               step-instrumenting)
 
                (:ignore
                ,@(unless (or variable (eq return :tail)) '(arg-locs))
                           (move rbp-tn new-fp) ; NB - now on new stack frame.
                           )))
 
+               (when step-instrumenting
+                 (emit-single-step-test)
+                 (inst jmp :eq DONE)
+                 (inst break single-step-around-trap))
+               DONE
+
                (note-this-location vop :call-site)
 
                (inst ,(if (eq return :tail) 'jmp 'call)
   (def unknown-key-arg-error unknown-key-arg-error
     sb!c::%unknown-key-arg-error key)
   (def nil-fun-returned-error nil-fun-returned-error nil fun))
+
+;;; Single-stepping
+
+(defun emit-single-step-test ()
+  ;; We use different ways of representing whether stepping is on on
+  ;; +SB-THREAD / -SB-THREAD: on +SB-THREAD, we use a slot in the
+  ;; thread structure. On -SB-THREAD we use the value of a static
+  ;; symbol. Things are done this way, since reading a thread-local
+  ;; slot from a symbol would require an extra register on +SB-THREAD,
+  ;; and reading a slot from a thread structure would require an extra
+  ;; register on -SB-THREAD. While this isn't critical for x86-64,
+  ;; it's more serious for x86.
+  #!+sb-thread
+  (inst cmp (make-ea :qword
+                     :base thread-base-tn
+                     :disp (* thread-stepping-slot n-word-bytes))
+        nil-value)
+  #!-sb-thread
+  (inst cmp (make-ea :qword
+                     :disp (+ nil-value (static-symbol-offset
+                                         'sb!impl::*stepping*)
+                              (* symbol-value-slot n-word-bytes)
+                              (- other-pointer-lowtag)))
+        nil-value))
+
+(define-vop (step-instrument-before-vop)
+  (:policy :fast-safe)
+  (:vop-var vop)
+  (:generator 3
+     (emit-single-step-test)
+     (inst jmp :eq DONE)
+     (inst break single-step-before-trap)
+     DONE
+     (note-this-location vop :step-before-vop)))
index 32d23e6..6140711 100644 (file)
       (#.halt-trap
        (nt "halt trap"))
       (#.fun-end-breakpoint-trap
-       (nt "function end breakpoint trap")))))
+       (nt "function end breakpoint trap"))
+      (#.single-step-around-trap
+       (nt "single-step trap (around)"))
+      (#.single-step-before-trap
+       (nt "single-step trap (before)")))))
 
 (define-instruction break (segment code)
   (:declare (type (unsigned-byte 8) code))
index 148763f..4f863fb 100644 (file)
   cerror
   breakpoint
   fun-end-breakpoint
-  single-step-breakpoint)
+  single-step-around
+  single-step-before)
 ;;; FIXME: It'd be nice to replace all the DEFENUMs with something like
 ;;;   (WITH-DEF-ENUM (:START 8)
 ;;;     (DEF-ENUM HALT-TRAP)
 ;;; for the benefit of anyone doing a lexical search for definitions
 ;;; of these symbols.
 
-(defenum (:prefix object-not- :suffix -trap :start 16)
+(defenum (:prefix object-not- :suffix -trap :start 24)
   list
   instance)
 
index 5f4f80a..3cca339 100644 (file)
                (:info
                ,@(unless (or variable (eq return :tail)) '(arg-locs))
                ,@(unless variable '(nargs))
-               ,@(when (eq return :fixed) '(nvals)))
+               ,@(when (eq return :fixed) '(nvals))
+               step-instrumenting)
 
                (:ignore
                ,@(unless (or variable (eq return :tail)) '(arg-locs))
                           (move ebp-tn new-fp) ; NB - now on new stack frame.
                           )))
 
+               (when step-instrumenting
+                 (emit-single-step-test)
+                 (inst jmp :eq DONE)
+                 (inst break single-step-around-trap))
+               DONE
+
                (note-this-location vop :call-site)
 
                (inst ,(if (eq return :tail) 'jmp 'call)
   (def unknown-key-arg-error unknown-key-arg-error
     sb!c::%unknown-key-arg-error key)
   (def nil-fun-returned-error nil-fun-returned-error nil fun))
+
+;;; Single-stepping
+
+(defun emit-single-step-test ()
+  ;; We use different ways of representing whether stepping is on on
+  ;; +SB-THREAD / -SB-THREAD: on +SB-THREAD, we use a slot in the
+  ;; thread structure. On -SB-THREAD we use the value of a static
+  ;; symbol. Things are done this way, since reading a thread-local
+  ;; slot from a symbol would require an extra register on +SB-THREAD,
+  ;; and reading a slot from a thread structure would require an extra
+  ;; register on -SB-THREAD.
+  #!+sb-thread
+  (progn
+    (inst fs-segment-prefix)
+    (inst cmp (make-ea :dword
+                       :disp (* thread-stepping-slot n-word-bytes))
+          nil-value))
+  #!-sb-thread
+  (inst cmp (make-ea :dword
+                     :disp (+ nil-value (static-symbol-offset
+                                         'sb!impl::*stepping*)
+                              (* symbol-value-slot n-word-bytes)
+                              (- other-pointer-lowtag)))
+        nil-value))
+
+(define-vop (step-instrument-before-vop)
+  (:policy :fast-safe)
+  (:vop-var vop)
+  (:generator 3
+     (emit-single-step-test)
+     (inst jmp :eq DONE)
+     (inst break single-step-before-trap)
+     DONE
+     (note-this-location vop :step-before-vop)))
index 0df6e5b..f55cf9c 100644 (file)
   cerror
   breakpoint
   fun-end-breakpoint
-  single-step-breakpoint
+  single-step-around
+  single-step-before
   #!+win32 context-restore) ;; HACK: The Win32 exception handling system does wrong things with this.
 ;;; FIXME: It'd be nice to replace all the DEFENUMs with something like
 ;;;   (WITH-DEF-ENUM (:START 8)
 ;;; for the benefit of anyone doing a lexical search for definitions
 ;;; of these symbols.
 
-(defenum (:prefix object-not- :suffix -trap :start 16)
+(defenum (:prefix object-not- :suffix -trap :start 24)
   list
   instance)
 
index edee7b3..9b7b318 100644 (file)
@@ -185,3 +185,16 @@ void *handle_fun_end_breakpoint(int signal, siginfo_t *info,
     return compute_pc(lra, fixnum_value(codeptr->constants[REAL_LRA_SLOT+1]));
 #endif
 }
+
+void
+handle_single_step_trap (os_context_t *context, int kind, int register_offset)
+{
+    fake_foreign_function_call(context);
+
+    funcall3(SymbolFunction(HANDLE_SINGLE_STEP_TRAP),
+             alloc_sap(context),
+             make_fixnum(kind),
+             make_fixnum(register_offset));
+
+    undo_fake_foreign_function_call(context);
+}
index 6554971..03e0891 100644 (file)
@@ -432,6 +432,16 @@ sigtrap_handler(int signal, siginfo_t *siginfo, os_context_t *context)
                             sizeof(unsigned int));
             break;
 
+        case trap_SingleStepAround:
+        case trap_SingleStepBefore:
+            {
+                int register_offset = code >> 5 & 0x1f;
+
+                handle_single_step_trap(context, trap, register_offset);
+
+                arch_skip_instruction(context);
+                break;
+            }
         default:
             interrupt_handle_now(signal, code, context);
             break;
index 6d5ddaa..a08c2ad 100644 (file)
@@ -399,6 +399,8 @@ create_thread_struct(lispobj initial_function) {
     }
     th->interrupt_data->pending_handler = 0;
     th->no_tls_value_marker=initial_function;
+
+    th->stepping = NIL;
     return th;
 }
 
index eaa1e23..6d7046c 100644 (file)
@@ -100,6 +100,8 @@ void arch_skip_instruction(os_context_t *context)
 
         case trap_PendingInterrupt:
         case trap_Halt:
+        case trap_SingleStepAround:
+        case trap_SingleStepBefore:
             /* only needed to skip the Code */
             break;
 
@@ -256,6 +258,14 @@ sigtrap_handler(int signal, siginfo_t *info, void *void_context)
             (unsigned long)handle_fun_end_breakpoint(signal, info, context);
         break;
 
+    case trap_SingleStepAround:
+    case trap_SingleStepBefore:
+        arch_skip_instruction(context);
+        /* On x86-64 the fdefn / function is always in RAX, so we pass
+         * 0 as the register_offset. */
+        handle_single_step_trap(context, trap, 0);
+        break;
+
     default:
         FSHOW((stderr,"/[C--trap default %d %d %x]\n",
                signal, code, context));
index ba0cf36..4540142 100644 (file)
@@ -107,6 +107,8 @@ void arch_skip_instruction(os_context_t *context)
 
         case trap_PendingInterrupt:
         case trap_Halt:
+        case trap_SingleStepAround:
+        case trap_SingleStepBefore:
             /* only needed to skip the Code */
             break;
 
@@ -304,6 +306,14 @@ sigtrap_handler(int signal, siginfo_t *info, void *void_context)
             (int)handle_fun_end_breakpoint(signal, info, context);
         break;
 
+    case trap_SingleStepAround:
+    case trap_SingleStepBefore:
+        arch_skip_instruction(context);
+        /* On x86 the fdefn / function is always in EAX, so we pass 0
+         * as the register_offset. */
+        handle_single_step_trap(context, trap, 0);
+        break;
+
     default:
         FSHOW((stderr,"/[C--trap default %d %d %x]\n",
                signal, trap, context));
index f01b9b3..ea5d8b7 100644 (file)
 
   ;; bug 353: This test fails at least most of the time for x86/linux
   ;; ca. 0.8.20.16. -- WHN
-  (with-test (:name (:undefined-function :bug-356)
-              :fails-on '(or (and :x86 :linux) :alpha))
+  (with-test (:name (:undefined-function :bug-353)
+              ;; This used to have fewer :fails-on features pre-0.9.16.38,
+              ;; but it turns out that the bug was just being masked by
+              ;; the presence of the IR1 stepper instrumentation (and
+              ;; is thus again failing now that the instrumentation is
+              ;; no more).
+              :fails-on '(or :x86 :x86-64 :alpha))
     (assert (verify-backtrace
              (lambda () (test #'not-optimized))
              (list *undefined-function-frame*
index f5664e3..81a00f5 100644 (file)
@@ -43,7 +43,7 @@
 ;;;; number.
 
 (run "cc"
-     #+x86-64 "-fPIC"
+     #+(and linux (or x86-64 ppc)) "-fPIC"
      "stack-alignment-offset.c" "-o" "stack-alignment-offset")
 
 (defparameter *good-offset*
@@ -53,7 +53,7 @@
 ;;;; Build the tool again, this time as a shared object, and load it
 
 (run "cc" "stack-alignment-offset.c"
-     #+x86-64 "-fPIC"
+     #+(and linux (or x86-64 ppc)) "-fPIC"
      #+darwin "-bundle" #-darwin "-shared"
      "-o" "stack-alignment-offset.so")
 
diff --git a/tests/step.impure.lisp b/tests/step.impure.lisp
new file mode 100644 (file)
index 0000000..f02f148
--- /dev/null
@@ -0,0 +1,180 @@
+;;;; This file is for testing the single-stepper.
+
+;;;; This software is part of the SBCL system. See the README file for
+;;;; more information.
+;;;;
+;;;; While most of SBCL is derived from the CMU CL system, the test
+;;;; files (like this one) were written from scratch after the fork
+;;;; from CMU CL.
+;;;;
+;;;; This software is in the public domain and is provided with
+;;;; absolutely no warranty. See the COPYING and CREDITS files for
+;;;; more information.
+
+(in-package :cl-user)
+
+;; No stepper support on some platforms.
+#-(or x86 x86-64 ppc)
+(sb-ext:quit :unix-status 104)
+
+(defun fib (x)
+  (declare (optimize debug))
+  (if (< x 2)
+      1
+      (+ (fib (1- x))
+         (fib (- x 2)))))
+
+(defvar *cerror-called* nil)
+
+(defun fib-break (x)
+  (declare (optimize debug))
+  (if (< x 2)
+      (progn
+        (unless *cerror-called*
+          (cerror "a" "b")
+          (setf *cerror-called* t))
+        1)
+      (+ (fib-break (1- x))
+         (fib-break (- x 2)))))
+
+(defun test-step-into ()
+  (let* ((results nil)
+         (expected '(("(< X 2)" :unknown)
+                     ("(- X 1)" :unknown)
+                     ("(FIB (1- X))" (2))
+                     ("(< X 2)" :unknown)
+                     ("(- X 1)" :unknown)
+                     ("(FIB (1- X))" (1))
+                     ("(< X 2)" :unknown)
+                     ("(- X 2)" :unknown)
+                     ("(FIB (- X 2))" (0))
+                     ("(< X 2)" :unknown)
+                     ("(+ (FIB (1- X)) (FIB (- X 2)))" :unknown)
+                     ("(- X 2)" :unknown)
+                     ("(FIB (- X 2))" (1))
+                     ("(< X 2)" :unknown)
+                     ("(+ (FIB (1- X)) (FIB (- X 2)))" :unknown)))
+         (*stepper-hook* (lambda (condition)
+                           (typecase condition
+                             (step-form-condition
+                              (push (list (step-condition-form condition)
+                                          (step-condition-args condition))
+                                    results)
+                              (invoke-restart 'step-into))))))
+    (step (fib 3))
+    (assert (equal expected (reverse results)))))
+
+(defun test-step-next ()
+  (let* ((results nil)
+         (expected '(("(< X 2)" :unknown)
+                     ("(- X 1)" :unknown)
+                     ("(FIB (1- X))" (2))
+                     ("(< X 2)" :unknown)
+                     ("(- X 1)" :unknown)
+                     ("(FIB (1- X))" (1))
+                     ("(- X 2)" :unknown)
+                     ("(FIB (- X 2))" (0))
+                     ("(+ (FIB (1- X)) (FIB (- X 2)))" :unknown)
+                     ("(- X 2)" :unknown)
+                     ("(FIB (- X 2))" (1))
+                     ("(+ (FIB (1- X)) (FIB (- X 2)))" :unknown)))
+         (count 0)
+         (*stepper-hook* (lambda (condition)
+                           (typecase condition
+                             (step-form-condition
+                              (push (list (step-condition-form condition)
+                                          (step-condition-args condition))
+                                    results)
+                              (if (< (incf count) 4)
+                                  (invoke-restart 'step-into)
+                                  (invoke-restart 'step-next)))))))
+    (step (fib 3))
+    (assert (equal expected (reverse results)))))
+
+(defun test-step-out ()
+  (let* ((results nil)
+         (expected '(("(< X 2)" :unknown)
+                     ("(- X 1)" :unknown)
+                     ("(FIB (1- X))" (2))
+                     ("(< X 2)" :unknown)
+                     ("(- X 2)" :unknown)
+                     ("(FIB (- X 2))" (1))
+                     ("(< X 2)" :unknown)
+                     ("(+ (FIB (1- X)) (FIB (- X 2)))" :unknown)))
+         (count 0)
+         (*stepper-hook* (lambda (condition)
+                           (typecase condition
+                             (step-form-condition
+                              (push (list (step-condition-form condition)
+                                          (step-condition-args condition))
+                                    results)
+                              (if (= (incf count) 4)
+                                  (invoke-restart 'step-out)
+                                  (invoke-restart 'step-into)))))))
+    (step (fib 3))
+    (assert (equal expected (reverse results)))))
+
+(defun test-step-start-from-break ()
+  (let* ((results nil)
+         (expected '(("(- X 2)" :unknown)
+                     ("(FIB-BREAK (- X 2))" (0))
+                     ("(< X 2)" :unknown)
+                     ("(+ (FIB-BREAK (1- X)) (FIB-BREAK (- X 2)))" :unknown)
+                     ("(- X 2)" :unknown)
+                     ("(FIB-BREAK (- X 2))" (1))
+                     ("(< X 2)" :unknown)
+                     ("(+ (FIB-BREAK (1- X)) (FIB-BREAK (- X 2)))" :unknown)))
+         (count 0)
+         (*stepper-hook* (lambda (condition)
+                           (typecase condition
+                             (step-form-condition
+                              (push (list (step-condition-form condition)
+                                          (step-condition-args condition))
+                                    results)
+                              (invoke-restart 'step-into))))))
+    (setf *cerror-called* nil)
+    (handler-bind ((error
+                    (lambda (c)
+                      (sb-impl::enable-stepping)
+                      (invoke-restart 'continue))))
+      (fib-break 3))
+    (assert (equal expected (reverse results)))))
+
+(defun test-step-frame ()
+  (let* ((count 0)
+         (*stepper-hook* (lambda (condition)
+                           (typecase condition
+                             (step-form-condition
+                              (let* ((frame (sb-di::find-stepped-frame))
+                                     (dfun (sb-di::frame-debug-fun frame))
+                                     (name (sb-di::debug-fun-name dfun)))
+                                (assert (equal name 'fib))
+                                (incf count)))))))
+    (step (fib 3))
+    (assert (= count 6))))
+
+(defun test-step-backtrace ()
+  (let* ((*stepper-hook* (lambda (condition)
+                           (typecase condition
+                             (step-form-condition
+                              (let ((*debug-io* (make-broadcast-stream)))
+                                (backtrace)))))))
+    (step (fib 3))))
+
+(handler-bind ((step-condition (lambda (c)
+                                 (funcall *stepper-hook* c))))
+  (with-test (:name :step-into)
+    (test-step-into))
+  (with-test (:name :step-next)
+    (test-step-next))
+  (with-test (:name :step-out)
+    (test-step-out))
+  (with-test (:name :step-start-from-break)
+    (test-step-start-from-break))
+  (with-test (:name :step-frame)
+    (test-step-frame))
+  (with-test (:name :step-backtrace)
+    (test-step-backtrace)))
+
+
+
index c4ab8bb..c439599 100644 (file)
@@ -17,4 +17,4 @@
 ;;; checkins which aren't released. (And occasionally for internal
 ;;; versions, especially for internal versions off the main CVS
 ;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"0.9.16.37"
+"0.9.16.38"