[Git][cmucl/cmucl][master] 2 commits: Fix #97: Use UD1 instruction instead of INT3

Raymond Toy (@rtoy) gitlab at common-lisp.net
Mon May 31 17:13:30 UTC 2021



Raymond Toy pushed to branch master at cmucl / cmucl


Commits:
138199bd by Raymond Toy at 2021-05-31T17:13:13+00:00
Fix #97: Use UD1 instruction instead of INT3

- - - - -
94a45e3f by Raymond Toy at 2021-05-31T17:13:13+00:00
Merge branch 'issue-97-define-ud2-inst' into 'master'

Fix #97: Use UD1 instruction instead of INT3

Closes #97

See merge request cmucl/cmucl!72
- - - - -


9 changed files:

- src/code/x86-vm.lisp
- src/compiler/x86/insts.lisp
- src/compiler/x86/macros.lisp
- src/compiler/x86/system.lisp
- src/lisp/arch.h
- src/lisp/breakpoint.c
- src/lisp/x86-arch.c
- src/lisp/x86-arch.h
- src/lisp/x86-assem.S


Changes:

=====================================
src/code/x86-vm.lisp
=====================================
@@ -237,12 +237,20 @@
   (with-alien ((scp (* unix:sigcontext) scp))
     (let ((pc (sigcontext-program-counter scp)))
       (declare (type system-area-pointer pc))
-      ;; using INT3 the pc is .. INT3 <here> code length bytes...
-      (let* ((length (sap-ref-8 pc 1))
+      ;; The pc should point to the start of the UD1 instruction.  So
+      ;; we have something like:
+      ;;
+      ;;   offset  contents
+      ;;   0       UD1 (contains the trap code)
+      ;;   3       length
+      ;;   4...    bytes
+      (let* ((length (sap-ref-8 pc 3))
 	     (vector (make-array length :element-type '(unsigned-byte 8))))
 	(declare (type (unsigned-byte 8) length)
 		 (type (simple-array (unsigned-byte 8) (*)) vector))
-	(copy-from-system-area pc (* vm:byte-bits 2)
+	;; Grab bytes after the length byte where the number of bytes is
+	;; given by value of the length byte.
+	(copy-from-system-area pc (* vm:byte-bits 4)
 			       vector (* vm:word-bits
 					 vm:vector-data-offset)
 			       (* length vm:byte-bits))


=====================================
src/compiler/x86/insts.lisp
=====================================
@@ -1779,7 +1779,7 @@
     (bit-test-reg/mem 24
 		      :default-printer '(:name :tab reg/mem ", " reg))
   (prefix	:field (byte 8 0)	:value #b0001111)
-  (op		:field (byte 3 11))
+  (op		:field (byte 8 8))
   ;;(test		:fields (list (byte 2 14) (byte 3 8)))
   (reg/mem	:fields (list (byte 2 22) (byte 3 16))
 		:type 'reg/mem)
@@ -1788,22 +1788,22 @@
   (imm))
 
 (define-instruction bt (segment src index)
-  (:printer bit-test-reg/mem ((op #b100)))
+  (:printer bit-test-reg/mem ((op #b10100011)))
   (:emitter
    (emit-bit-test-and-mumble segment src index #b100)))
 
 (define-instruction btc (segment src index)
-  (:printer bit-test-reg/mem ((op #b111)))
+  (:printer bit-test-reg/mem ((op #b10111011)))
   (:emitter
    (emit-bit-test-and-mumble segment src index #b111)))
 
 (define-instruction btr (segment src index)
-  (:printer bit-test-reg/mem ((op #b110)))
+  (:printer bit-test-reg/mem ((op #b10110011)))
   (:emitter
    (emit-bit-test-and-mumble segment src index #b110)))
 
 (define-instruction bts (segment src index)
-  (:printer bit-test-reg/mem ((op #b101)))
+  (:printer bit-test-reg/mem ((op #b10101011)))
   (:emitter
    (emit-bit-test-and-mumble segment src index #b101)))
 
@@ -2061,6 +2061,26 @@
  (op :field (byte 8 0))
  (code :field (byte 8 8)))
 
+
+;; The UD1 instruction.  The mod bits of the mod r/m byte MUST be #b11
+;; so that the reg/mem field is actually a register.  This is a hack
+;; to allow us to print out the reg/mem reg as a 32-bit reg.
+;;
+;; While the instruction looks like an ext-reg-reg/mem format with
+;; fixed width value of 1, it isn't because we need to disassemble the
+;; reg/mem field as a 32-bit reg. ext-reg-reg/mem needs a width prefix
+;; byte to specify that, and we definitely don't want that.  Hence,
+;; use a special instruction format for the UD1 instruction.
+(disassem:define-instruction-format
+    (ud1 24 :default-printer '(:name :tab reg ", " reg/mem))
+  (prefix    :field (byte 8 0) :value #b00001111)
+  (op        :field (byte 8 8) :value #b10111001)
+  ;; The mod bits ensure that the reg/mem field is interpreted as a
+  ;; register, not memory.
+  (reg/mem   :field (byte 3 16) :type 'word-reg)
+  (reg	     :field (byte 3 19) :type 'word-reg)
+  (mod       :field (byte 2 22) :value #b11))
+
 (defun snarf-error-junk (sap offset &optional length-only)
   (let* ((length (system:sap-ref-8 sap offset))
          (vector (make-array length :element-type '(unsigned-byte 8))))
@@ -2091,53 +2111,64 @@
                        (sc-offsets)
                        (lengths))))))))
 
-(defmacro break-cases (breaknum &body cases)
-  (let ((bn-temp (gensym)))
-    (collect ((clauses))
-      (dolist (case cases)
-        (clauses `((= ,bn-temp ,(car case)) ,@(cdr case))))
-      `(let ((,bn-temp ,breaknum))
-         (cond ,@(clauses))))))
-
-(defun break-control (chunk inst stream dstate)
+(defun ud1-control (chunk inst stream dstate)
   (declare (ignore inst))
   (flet ((nt (x) (if stream (disassem:note x dstate))))
-    (case (byte-imm-code chunk dstate)
-      (#.vm:error-trap
-       (nt "Error trap")
-       (disassem:handle-break-args #'snarf-error-junk stream dstate))
-      (#.vm:cerror-trap
-       (nt "Cerror trap")
-       (disassem:handle-break-args #'snarf-error-junk stream dstate))
-      (#.vm:breakpoint-trap
-       (nt "Breakpoint trap"))
-      (#.vm:pending-interrupt-trap
-       (nt "Pending interrupt trap"))
-      (#.vm:halt-trap
-       (nt "Halt trap"))
-      (#.vm:function-end-breakpoint-trap
-       (nt "Function end breakpoint trap"))
-    )))
-
-;; This is really the int3 instruction.
-(define-instruction break (segment code)
+    (let ((code (ldb (byte 6 16) chunk)))
+      (ecase code
+	(#.vm:error-trap
+	 (nt #.(format nil "Trap ~D: Error trap" vm:error-trap))
+	 (disassem:handle-break-args #'snarf-error-junk stream dstate))
+	(#.vm:cerror-trap
+	 (nt #.(format nil "Trap ~D: Cerror trap" vm:cerror-trap))
+	 (disassem:handle-break-args #'snarf-error-junk stream dstate))
+	(#.vm:pending-interrupt-trap
+	 (nt #.(format nil "Trap ~D: Pending interrupt trap" vm:pending-interrupt-trap)))
+	(#.vm:halt-trap
+	 (nt #.(format nil "Trap ~D: Halt trap" vm:halt-trap)))
+	(#.vm:function-end-breakpoint-trap
+	 (nt #.(format nil "Trap ~D: Function end breakpoint trap"
+		       vm:function-end-breakpoint-trap)))))))
+
+;; The ud1 instruction where we smash the code (trap type) into the
+;; low 6 bits of the mod r/m byte.  The mod bits are set to #b11 to
+;; make sure the reg/mem part is interpreted to be a register and not
+;; memory.
+(define-instruction ud1 (segment code)
   (:declare (type (unsigned-byte 8) code))
-  (:printer byte-imm ((op #b11001100)) '(:name :tab code)
-	    :control #'break-control)
+  (:printer ud1 ((op #b10111001) (reg nil :type 'word-reg))
+	    :default
+	    :control #'ud1-control)
   (:emitter
-   (emit-byte segment #b11001100)
-   (emit-byte segment code)))
+   ;; We should not be using the breakpoint trap with UD1 anymore.
+   ;; Breakpoint traps are handled in C now, using plain int3.
+   (assert (/= code vm:breakpoint-trap))
 
+   ;; Emit the bytes of the instruction.
+   (emit-byte segment #x0f)
+   (emit-byte segment #xb9)
+   (emit-mod-reg-r/m-byte segment
+			  #b11
+			  (ldb (byte 3 3) code)
+			  (ldb (byte 3 0) code))))
+
+;; Handles both int and int3.  To get int3 you have to say (inst int
+;; 3).  But int3 should not be used in Lisp code.  This is mainly so
+;; that int3 gets disassembled correctly if a breakpoint has been set
+;; in Lisp code.  (But in general the disassembly will be messed up
+;; because the following byte will in general be the second byte of
+;; some instruction, and not the first byte of an instruction.)
 (define-instruction int (segment number)
   (:declare (type (unsigned-byte 8) number))
   (:printer byte-imm ((op #b11001101)))
   (:emitter
-   (etypecase number
-     ((member 3)
-      (emit-byte segment #b11001100))
-     ((unsigned-byte 8)
-      (emit-byte segment #b11001101)
-      (emit-byte segment number)))))
+   (emit-byte segment #b11001101)
+   (emit-byte segment number)))
+
+(define-instruction int3 (segment)
+  (:printer byte ((op #b11001100)))
+  (:emitter
+   (emit-byte segment #b11001100)))
 
 (define-instruction into (segment)
   (:printer byte ((op #b11001110)))


=====================================
src/compiler/x86/macros.lisp
=====================================
@@ -243,12 +243,10 @@
   (defun emit-error-break (vop kind code values)
     (let ((vector (gensym))
 	  (length (gensym)))
-      `((inst int 3)				; i386 breakpoint instruction
-	;; The return PC points here; note the location for the debugger.
-	(let ((vop ,vop))
+      `((let ((vop ,vop))
   	  (when vop
-		(note-this-location vop :internal-error)))
-	(inst byte ,kind)			; eg trap_Xyyy
+	    (note-this-location vop :internal-error)))
+	(inst ud1 ,kind)		; eg trap_Xyyy
 	(let ((,vector (make-array 8 :element-type '(unsigned-byte 8)
 				   :fill-pointer 0 :adjustable t)))
 	  (write-var-integer (error-number-or-lose ',code) ,vector)
@@ -342,7 +340,7 @@
 				    (- other-pointer-type)))
 	      0)
 	(inst jmp :eq ,label)
-	(inst break pending-interrupt-trap)
+	(inst ud1 pending-interrupt-trap)
 	(emit-label ,label)))))
 
 


=====================================
src/compiler/x86/system.lisp
=====================================
@@ -284,11 +284,11 @@
   (:policy :fast-safe)
   (:translate unix::do-pending-interrupt)
   (:generator 1
-    (inst break pending-interrupt-trap)))
+    (inst ud1 pending-interrupt-trap)))
 
 (define-vop (halt)
   (:generator 1
-    (inst break halt-trap)))
+    (inst ud1 halt-trap)))
 
 (defknown float-wait () (values))
 (define-vop (float-wait)


=====================================
src/lisp/arch.h
=====================================
@@ -13,15 +13,39 @@
 
 extern char *arch_init(fpu_mode_t);
 
+/*
+ * Skip over the trap instructions for an error trap and also skip
+ * over anly following bytes used to encode information for an
+ * error-trap or cerror-trap.  The PC in the context is set to address
+ * just past the trap instruction and data bytes (if any).
+ */
 extern void arch_skip_instruction(os_context_t * scp);
+
 extern boolean arch_pseudo_atomic_atomic(os_context_t * scp);
 extern void arch_set_pseudo_atomic_interrupted(os_context_t * scp);
 extern os_vm_address_t arch_get_bad_addr(HANDLER_ARGS);
 extern unsigned char *arch_internal_error_arguments(os_context_t * scp);
+
+/*
+ * Install an architecture-dependent breakpoint instruction at the
+ * given PC address.  This also returns the bytes that were
+ * overwritten by the breakpoint instruction so that the original
+ * instruction can be restored once the breakpoint has been handled.
+ */
 extern unsigned long arch_install_breakpoint(void *pc);
+
 extern void arch_remove_breakpoint(void *pc, unsigned long orig_inst);
 extern void arch_install_interrupt_handlers(void);
+
+/*
+ * This is called when we need to continue after a breakpoint.  The
+ * original instruction in |orig_inst| is put back.  Then things are
+ * set up so that we can run again and after this instruction is run,
+ * we trap again so that the original breakpoint can be replaced.  How
+ * this is done is architecture-dependent.
+ */
 extern void arch_do_displaced_inst(os_context_t * scp, unsigned long orig_inst);
+
 extern lispobj funcall0(lispobj function);
 extern lispobj funcall1(lispobj function, lispobj arg0);
 extern lispobj funcall2(lispobj function, lispobj arg0, lispobj arg1);


=====================================
src/lisp/breakpoint.c
=====================================
@@ -192,6 +192,8 @@ compute_offset(os_context_t * scp, lispobj code, boolean function_end)
 static int
 compute_offset(os_context_t * scp, lispobj code, boolean function_end)
 {
+    DPRINTF(debug_handlers, (stderr, "compute_offset: code = 0x%lx\n", code));
+    
     if (code == NIL)
 	return 0;
     else {
@@ -206,11 +208,20 @@ compute_offset(os_context_t * scp, lispobj code, boolean function_end)
 
 	code_start = (unsigned long) codeptr
 	    + HeaderValue(codeptr->header) * sizeof(lispobj);
+
+        DPRINTF(debug_handlers,
+                (stderr, "compute_offset: pc = 0x%lx, code_start = 0x%lx\n",
+                 pc, code_start));
+        
 	if (pc < code_start)
 	    return 0;
 	else {
 	    int offset = pc - code_start;
 
+            DPRINTF(debug_handlers,
+                    (stderr, "compute_offset: offset %d, size = %ld\n",
+                     offset, codeptr->code_size));
+            
 	    if (offset >= codeptr->code_size) {
                 return 0;
 	    } else {
@@ -250,6 +261,10 @@ handle_breakpoint(int signal, int subcode, os_context_t * scp)
 
     code = find_code(scp);
 
+    DPRINTF(debug_handlers,
+            (stderr, "handle breakpoint: offset %d\n",
+             compute_offset(scp, code, 0)));
+    
     /*
      * Don't disallow recursive breakpoint traps.  Otherwise, we can't
      * use debugger breakpoints anywhere in here.


=====================================
src/lisp/x86-arch.c
=====================================
@@ -23,7 +23,19 @@
 
 #define BREAKPOINT_INST 0xcc	/* INT3 */
 
-unsigned long fast_random_state = 1;
+/*
+ * The first two bytes of the UD1 instruction.  The mod r/m byte isn't
+ * included here.
+ */
+static const unsigned char ud1[] = {0x0f, 0xb9};
+      
+
+/*
+ * Set to positive value to enabled debug prints related to the sigill
+ * and sigtrap handlers.  Also enables prints related to handling of
+ * breakpoints.
+ */
+unsigned int debug_handlers = 0;
 
 #if defined(SOLARIS)
 /*
@@ -129,32 +141,42 @@ arch_init(fpu_mode_t mode)
 
 
 /*
- * Assuming we get here via an INT3 xxx instruction, the PC now
- * points to the interrupt code (lisp value) so we just move past
- * it. Skip the code, then if the code is an error-trap or
- * Cerror-trap then skip the data bytes that follow.
+ * Skip the UD1 instruction, and any data bytes associated with the
+ * trap.
  */
-
 void
 arch_skip_instruction(os_context_t * context)
 {
     int vlen, code;
 
-    DPRINTF(0, (stderr, "[arch_skip_inst at %lx>]\n", SC_PC(context)));
+    DPRINTF(debug_handlers,
+            (stderr, "[arch_skip_inst at %lx>]\n", SC_PC(context)));
+
+    /* Get the address of the beginning of the UD1 instruction */
+    char* pc = (char *) SC_PC(context);
+    
+    /*
+     * Skip over the part of the UD1 inst (0x0f, 0xb9) so we can get to the mod r/m byte
+     */
+    pc += sizeof(ud1);
+
+    code = *pc++;
+    SC_PC(context) = (unsigned long) pc;
 
-    /* Get and skip the lisp error code. */
-    code = *(char *) SC_PC(context)++;
     switch (code) {
       case trap_Error:
       case trap_Cerror:
 	  /* Lisp error arg vector length */
-	  vlen = *(char *) SC_PC(context)++;
+          vlen = *pc++;
+          SC_PC(context) = (unsigned long) pc;
+          
 	  /* Skip lisp error arg data bytes */
-	  while (vlen-- > 0)
-	      SC_PC(context)++;
+          SC_PC(context) = (unsigned long) (pc + vlen);
 	  break;
 
       case trap_Breakpoint:
+          lose("Unexpected breakpoint trap in arch_skip_instruction\n");
+          break;
       case trap_FunctionEndBreakpoint:
 	  break;
 
@@ -168,7 +190,8 @@ arch_skip_instruction(os_context_t * context)
 	  break;
     }
 
-    DPRINTF(0, (stderr, "[arch_skip_inst resuming at %lx>]\n", SC_PC(context)));
+    DPRINTF(debug_handlers,
+            (stderr, "[arch_skip_inst resuming at %lx>]\n", SC_PC(context)));
 }
 
 unsigned char *
@@ -191,13 +214,19 @@ arch_set_pseudo_atomic_interrupted(os_context_t * context)
 
 
 
+/*
+ * Installs a breakpoint (INT3) at |pc|.  We return the byte that was
+ * replaced by the int3 instruction.
+ */
 unsigned long
 arch_install_breakpoint(void *pc)
 {
-    unsigned long result = *(unsigned long *) pc;
+    unsigned long result = *(unsigned char *) pc;
+    *(unsigned char *) pc = BREAKPOINT_INST;
 
-    *(char *) pc = BREAKPOINT_INST;	/* x86 INT3       */
-    *((char *) pc + 1) = trap_Breakpoint;	/* Lisp trap code */
+    DPRINTF(debug_handlers,
+            (stderr, "arch_install_breakpoint at %p, old code = 0x%lx\n",
+             pc, result));
 
     return result;
 }
@@ -205,8 +234,14 @@ arch_install_breakpoint(void *pc)
 void
 arch_remove_breakpoint(void *pc, unsigned long orig_inst)
 {
-    *((char *) pc) = orig_inst & 0xff;
-    *((char *) pc + 1) = (orig_inst & 0xff00) >> 8;
+    DPRINTF(debug_handlers,
+            (stderr, "arch_remove_breakpoint: %p orig %lx\n",
+             pc, orig_inst));
+
+    /*
+     * Just restore the byte from orig_inst.
+     */
+    *(unsigned char *) pc = orig_inst & 0xff;
 }
 
 
@@ -224,20 +259,44 @@ unsigned int single_step_save2;
 unsigned int single_step_save3;
 #endif
 
+/*
+ * This is called when we need to continue after a breakpoint.  This
+ * works by putting the original byte back into the code, and then
+ * enabling single-step mode to step one instruction.  When we return,
+ * the instruction will get run and a sigtrap will get triggered when
+ * the one instruction is done.
+ *
+ * TODO: Be more like other archs where the original inst is put back,
+ * and the next inst is replaced with a afterBreakpoint trap.  When we
+ * run, the afterBreakpoint trap is hit at the next instruction and
+ * then we can put back the original breakpoint and replace the
+ * afterBreakpoint trap with the original inst there too.
+ *
+ * For x86, this means computing how many bytes are used in the
+ * current instruction, and then placing an int3 (or maybe ud1) after
+ * it.
+ */
 void
 arch_do_displaced_inst(os_context_t * context, unsigned long orig_inst)
 {
-    unsigned int *pc = (unsigned int *) SC_PC(context);
+    unsigned char *pc = (unsigned char *) SC_PC(context);
 
+    DPRINTF(debug_handlers,
+            (stderr, "arch_do_displaced_inst: pc %p orig_inst %lx\n",
+             pc, orig_inst));
+    
     /*
      * Put the original instruction back.
      */
 
-    *((char *) pc) = orig_inst & 0xff;
-    *((char *) pc + 1) = (orig_inst & 0xff00) >> 8;
+    *pc = orig_inst & 0xff;
 
+    /*
+     * If we have the SC_EFLAGS macro, we can enable single-stepping
+     * by setting the bit.  Otherwise, we need a more complicated way
+     * of enabling single-stepping.
+     */
 #ifdef SC_EFLAGS
-    /* Enable single-stepping */
     SC_EFLAGS(context) |= 0x100;
 #else
 
@@ -274,53 +333,31 @@ arch_do_displaced_inst(os_context_t * context, unsigned long orig_inst)
 }
 
 
+/*
+ * Handles the ud1 instruction from lisp that is used to signal
+ * errors.  In particular, this does not handle the breakpoint traps.
+ */
 void
-sigtrap_handler(HANDLER_ARGS)
+sigill_handler(HANDLER_ARGS)
 {
     unsigned int trap;
     os_context_t* os_context = (os_context_t *) context;
-#if 0
-    fprintf(stderr, "x86sigtrap: %8x %x\n",
-            SC_PC(os_os_context), *(unsigned char *) (SC_PC(os_context) - 1));
-    fprintf(stderr, "sigtrap(%d %d %x)\n", signal, CODE(code), os_context);
-#endif
 
-    if (single_stepping && (signal == SIGTRAP)) {
-#if 0
-	fprintf(stderr, "* Single step trap %p\n", single_stepping);
-#endif
-
-#ifdef SC_EFLAGS
-	/* Disable single-stepping */
-	SC_EFLAGS(os_context) ^= 0x100;
-#else
-	/* Un-install single step helper instructions. */
-	*(single_stepping - 3) = single_step_save1;
-	*(single_stepping - 2) = single_step_save2;
-	*(single_stepping - 1) = single_step_save3;
-        DPRINTF(0, (stderr, "Uninstalling helper instructions\n"));
-#endif
-
-	/*
-	 * Re-install the breakpoint if possible.
-	 */
-	if ((int) SC_PC(os_context) == (int) single_stepping + 1)
-	    fprintf(stderr, "* Breakpoint not re-install\n");
-	else {
-	    char *ptr = (char *) single_stepping;
-
-	    ptr[0] = BREAKPOINT_INST;	/* x86 INT3 */
-	    ptr[1] = trap_Breakpoint;
-	}
-
-	single_stepping = NULL;
-	return;
-    }
+    DPRINTF(debug_handlers,
+            (stderr,"sigill: fp=%lx sp=%lx pc=%lx { %x, %x, %x, %x, %x }\n",
+             SC_REG(context, reg_FP),
+             SC_REG(context, reg_SP),
+             SC_PC(context),
+             *((unsigned char*)SC_PC(context) + 0), /* 0x0F */
+             *((unsigned char*)SC_PC(context) + 1), /* 0x0B */
+             *((unsigned char*)SC_PC(context) + 2),
+             *((unsigned char*)SC_PC(context) + 3),
+             *((unsigned char*)SC_PC(context) + 4)));
 
+    
     /* This is just for info in case monitor wants to print an approx */
     current_control_stack_pointer = (unsigned long *) SC_SP(os_context);
 
-
     /*
      * In many places in the switch below, we eventually throw instead
      * of returning from the signal handler.  So, just in case, set
@@ -329,86 +366,181 @@ sigtrap_handler(HANDLER_ARGS)
     RESTORE_FPU(os_context);
 
     /*
-     * On entry %eip points just after the INT3 byte and aims at the
-     * 'kind' value (eg trap_Cerror). For error-trap and Cerror-trap a
-     * number of bytes will follow, the first is the length of the byte
-     * arguments to follow.
+     * On entry %eip points just to the beginning of the UD1
+     * instruction.  For error-trap and cerror-trap a number of bytes
+     * will follow, the first is the length of the byte arguments to
+     * follow.
      */
 
-    trap = *(unsigned char *) SC_PC(os_context);
+    DPRINTF(debug_handlers,
+            (stderr, "pc %x\n",  *(unsigned short *)SC_PC(context)));
 
-    switch (trap) {
+    /*
+     * If the trapping instruction is UD1, assume it's a Lisp trap
+     * that we handle here.  Otherwise, just call interrupt_handle_now
+     * for other cases.
+     */
+    if (memcmp((void *)SC_PC(context), ud1, sizeof(ud1)) == 0) {
+      /*
+       * This must match what the lisp code is doing.  The trap
+       * number is placed in the low 6-bits of the 3rd byte of the
+       * instruction.
+       */
+      trap = *(((char *)SC_PC(context)) + 2) & 0x3f;
+
+      DPRINTF(debug_handlers, (stderr, "code = %x\n", trap));
+
+      switch (trap) {
       case trap_PendingInterrupt:
-	  DPRINTF(0, (stderr, "<trap Pending Interrupt.>\n"));
-	  arch_skip_instruction(os_context);
-	  interrupt_handle_pending(os_context);
-	  break;
+        DPRINTF(debug_handlers, (stderr, "<trap Pending Interrupt.>\n"));
+        arch_skip_instruction(os_context);
+        interrupt_handle_pending(os_context);
+        break;
 
       case trap_Halt:
-	  {
-              FPU_STATE(fpu_state);
-              save_fpu_state(fpu_state);
+        {
+          FPU_STATE(fpu_state);
+          save_fpu_state(fpu_state);
 
-	      fake_foreign_function_call(os_context);
-	      lose("%%primitive halt called; the party is over.\n");
-	      undo_fake_foreign_function_call(os_context);
+          fake_foreign_function_call(os_context);
+          lose("%%primitive halt called; the party is over.\n");
+          undo_fake_foreign_function_call(os_context);
 
-              restore_fpu_state(fpu_state);
-	      arch_skip_instruction(os_context);
-	      break;
-	  }
+          restore_fpu_state(fpu_state);
+          arch_skip_instruction(os_context);
+          break;
+        }
 
       case trap_Error:
       case trap_Cerror:
-	  DPRINTF(0, (stderr, "<trap Error %x>\n", CODE(code)));
-	  interrupt_internal_error(signal, code, os_context, CODE(code) == trap_Cerror);
-	  break;
+        DPRINTF(debug_handlers, (stderr, "<trap Error %x>\n", CODE(code)));
+        interrupt_internal_error(signal, code, os_context, CODE(code) == trap_Cerror);
+        break;
 
       case trap_Breakpoint:
-#if 0
-	  fprintf(stderr, "*C break\n");
-#endif
-	  SC_PC(os_context) -= 1;
-
-	  handle_breakpoint(signal, CODE(code), os_context);
-#if 0
-	  fprintf(stderr, "*C break return\n");
-#endif
-	  break;
+        lose("Unexpected breakpoint trap in sigill-hander.\n");
+        break;
 
       case trap_FunctionEndBreakpoint:
-	  SC_PC(os_context) -= 1;
-	  SC_PC(os_context) =
-	      (int) handle_function_end_breakpoint(signal, CODE(code), os_context);
-	  break;
+        SC_PC(os_context) =
+          (int) handle_function_end_breakpoint(signal, CODE(code), os_context);
+        break;
 
 #ifdef trap_DynamicSpaceOverflowWarning
       case trap_DynamicSpaceOverflowWarning:
-	  interrupt_handle_space_overflow(SymbolFunction
-					  (DYNAMIC_SPACE_OVERFLOW_WARNING_HIT),
-					  os_context);
-	  break;
+        interrupt_handle_space_overflow(SymbolFunction
+                                        (DYNAMIC_SPACE_OVERFLOW_WARNING_HIT),
+                                        os_context);
+        break;
 #endif
 #ifdef trap_DynamicSpaceOverflowError
       case trap_DynamicSpaceOverflowError:
-	  interrupt_handle_space_overflow(SymbolFunction
-					  (DYNAMIC_SPACE_OVERFLOW_ERROR_HIT),
-					  os_context);
-	  break;
+        interrupt_handle_space_overflow(SymbolFunction
+                                        (DYNAMIC_SPACE_OVERFLOW_ERROR_HIT),
+                                        os_context);
+        break;
 #endif
       default:
-	  DPRINTF(0,
-		  (stderr, "[C--trap default %d %d %p]\n", signal, CODE(code),
-		   os_context));
-	  interrupt_handle_now(signal, code, os_context);
-	  break;
+        DPRINTF(debug_handlers,
+                (stderr, "[C--trap default %d %d %p]\n", signal, CODE(code),
+                 os_context));
+        interrupt_handle_now(signal, code, os_context);
+        break;
+      }
+    } else {
+      interrupt_handle_now(signal, code, os_context);
+    }
+}
+
+/*
+ * Handles the breakpoint trap (int3) and also single-stepping
+ */
+void
+sigtrap_handler(HANDLER_ARGS) 
+{
+    os_context_t* os_context = (os_context_t *) context;
+
+    DPRINTF(debug_handlers,
+            (stderr,"sigtrap: fp=%lx sp=%lx pc=%lx { %x, %x, %x, %x, %x }\n",
+             SC_REG(context, reg_FP),
+             SC_REG(context, reg_SP),
+             SC_PC(context),
+             *((unsigned char*)SC_PC(context) + 0), /* 0x0F */
+             *((unsigned char*)SC_PC(context) + 1), /* 0x0B */
+             *((unsigned char*)SC_PC(context) + 2),
+             *((unsigned char*)SC_PC(context) + 3),
+             *(unsigned char*)(SC_PC(context) + 4)));
+
+    if (single_stepping) {
+        /*
+         * We were single-stepping so we now need to disable
+         * single-stepping.  We want to put back the breakpoint (int3)
+         * instruction so that the next time the breakpoint will be
+         * hit again as expected.
+         */
+	DPRINTF(debug_handlers, (stderr, "* Single step trap %p\n", single_stepping));
+
+#ifdef SC_EFLAGS
+	/* Disable single-stepping */
+	SC_EFLAGS(os_context) ^= 0x100;
+#else
+	/* Un-install single step helper instructions. */
+	*(single_stepping - 3) = single_step_save1;
+	*(single_stepping - 2) = single_step_save2;
+	*(single_stepping - 1) = single_step_save3;
+        DPRINTF(0, (stderr, "Uninstalling helper instructions\n"));
+#endif
+
+	/*
+	 * Re-install the breakpoint if possible.
+	 */
+        DPRINTF(debug_handlers,
+                (stderr, "* Maybe reinstall breakpoint for pc %p with single_stepping %p\n",
+                 (void*) SC_PC(os_context), single_stepping));
+        
+        /*
+         * Lose if single-stepping didn't move us past where the
+         * breakpoint instruction was inserted.
+         */
+        if ((unsigned long) SC_PC(os_context) <= (unsigned long) single_stepping) {
+            lose("Single-stepping did not advance past the breakpoint at %p\n",
+                 single_stepping);
+        }
+
+        /*
+         * Put back the breakpoint since we skipped over it.
+         */
+        char *ptr = (char *) single_stepping;
+        ptr[0] = BREAKPOINT_INST;	/* x86 INT3 */
+
+	single_stepping = NULL;
+	return;
     }
+
+    /*
+     * We weren't single-stepping, so this we've just hit the breakpoint (int3).  Handle it.
+     */
+    DPRINTF(debug_handlers, (stderr, "*C break\n"));
+
+    /*
+     * The int3 instruction causes a trap that leaves us just after
+     * the instruction.  Backup one so we're at the beginning.  This
+     * is really important so that when we handle the breakpoint, the
+     * offset of the instruction matches where Lisp thinks the
+     * breakpoint was placed.
+     */
+    SC_PC(os_context) -= 1;
+
+    handle_breakpoint(signal, CODE(code), os_context);
+
+    DPRINTF(debug_handlers, (stderr, "*C break return\n"));
 }
 
+
 void
 arch_install_interrupt_handlers(void)
 {
-    interrupt_install_low_level_handler(SIGILL, sigtrap_handler);
+    interrupt_install_low_level_handler(SIGILL, sigill_handler);
     interrupt_install_low_level_handler(SIGTRAP, sigtrap_handler);
 }
 


=====================================
src/lisp/x86-arch.h
=====================================
@@ -11,6 +11,13 @@
 extern int arch_support_sse2(void);
 extern boolean os_support_sse2(void);
 
+/*
+ * Set to non-zero to enable debug prints for debugging the sigill and
+ * sigtrap handlers and for debugging breakpoints.
+ */
+extern unsigned int debug_handlers;
+
+
 /*
  * Define macro to allocate a local array of the appropriate size
  * where the fpu state can be stored.


=====================================
src/lisp/x86-assem.S
=====================================
@@ -18,6 +18,21 @@
 #include "internals.h"
 #include "lispregs.h"
 
+/*
+ * Emit the appropriate instruction used for implementing traps.
+ * Currently, this is the UD1 instruction.  However, it make it
+ * easy to add the trap code, use a sequence of bytes.  The code
+ * is smashed into the mod r/m byte with the mod bits set to
+ * #b11.  This MUST be coordinated with the Lisp code and the C
+ * code.
+ *
+ * Also, clang doesn't recognize the ud1 instruction.
+ */
+#define UD1(code) \
+	.byte 0x0f		; \
+	.byte 0xb9		; \
+	.byte 0xc0 + code
+
 /* Minimize conditionalization for different OS naming schemes */
 #ifdef DARWIN	
 #define GNAME(var) _##var
@@ -244,8 +259,7 @@ ENDFUNC(sse_restore)
  * The undefined-function trampoline.
  */
 FUNCDEF(undefined_tramp)
-	INT3
-	.byte	trap_Error
+	UD1(trap_Error)
         /* Number of argument bytes */
         .byte   2
 	.byte	UNDEFINED_SYMBOL_ERROR
@@ -286,32 +300,28 @@ multiple_value_return:
 	
 	.globl GNAME(function_end_breakpoint_trap)
 GNAME(function_end_breakpoint_trap):
-	INT3
-	.byte 	trap_FunctionEndBreakpoint
+	UD1(trap_FunctionEndBreakpoint)
 	hlt			# Should never return here.
+ENDFUNC(function_end_breakpoint_trap)
 
 	.globl GNAME(function_end_breakpoint_end)
 GNAME(function_end_breakpoint_end):
 
-
 FUNCDEF(do_pending_interrupt)
-	INT3
-	.byte 	trap_PendingInterrupt
+	UD1(trap_PendingInterrupt)
 	ret
 ENDFUNC(do_pending_interrupt)
 	
 #ifdef trap_DynamicSpaceOverflowError
 FUNCDEF(do_dynamic_space_overflow_error)
-	INT3
-	.byte 	trap_DynamicSpaceOverflowError
+	UD1(trap_DynamicSpaceOverflowError)
 	ret
 ENDFUNC(do_dynamic_space_overflow_error)
 #endif				
 	
 #ifdef trap_DynamicSpaceOverflowWarning
 FUNCDEF(do_dynamic_space_overflow_warning)
-	INT3
-	.byte 	trap_DynamicSpaceOverflowWarning
+	UD1(trap_DynamicSpaceOverflowWarning)
 	ret
 ENDFUNC(do_dynamic_space_overflow_warning)
 #endif				
@@ -493,8 +503,7 @@ FUNCDEF(undefined_foreign_symbol_trap)
         movl 8(%ebp),%eax
 
 	/* Now trap to Lisp */
-	INT3
-	.byte	trap_Error
+	UD1(trap_Error)
         /* Number of argument bytes */
         .byte   2
 	.byte	UNDEFINED_FOREIGN_SYMBOL_ERROR



View it on GitLab: https://gitlab.common-lisp.net/cmucl/cmucl/-/compare/2589cd0c29215133eac897d10f6f880d76bc1b75...94a45e3f8478710d65d84e292f58083ad394d473

-- 
View it on GitLab: https://gitlab.common-lisp.net/cmucl/cmucl/-/compare/2589cd0c29215133eac897d10f6f880d76bc1b75...94a45e3f8478710d65d84e292f58083ad394d473
You're receiving this email because of your account on gitlab.common-lisp.net.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/cmucl-cvs/attachments/20210531/05fb1c6c/attachment-0001.html>


More information about the cmucl-cvs mailing list