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/2589cd0c29215133eac897d...