mirror of
git://git.code.sf.net/p/sbcl/sbcl
synced 2026-09-10 15:36:41 -04:00
x86-64: add another way to avoid POSIX signals for internal errors
In you case you need to debug code which actually gets a SIGILL, so UD2 or INTO aren't the best choice of trap instruction. This doesn't pass the regression suite, but it's enough to help debug a crashing self-build using gdb. Add --without-int3-breakpoints in make-config to use this feature. Works only if #+linux at present.
This commit is contained in:
parent
c3dd14b430
commit
aa44aa035f
|
|
@ -1,3 +1,4 @@
|
|||
:int3-breakpoints
|
||||
:64-bit
|
||||
:gencgc
|
||||
:use-cons-region
|
||||
|
|
|
|||
|
|
@ -5,6 +5,33 @@
|
|||
|
||||
(in-package "SB-VM")
|
||||
|
||||
;;; The SYNCHRONOUS-TRAP routine has nearly the same effect as executing INT3
|
||||
;;; but is more friendly to gdb. There may be some subtle bugs with regard to
|
||||
;;; blocking/unblocking of async signals which arrive nearly around the same
|
||||
;;; time as a synchronous trap.
|
||||
#+sw-int-avoidance ; "software interrupt avoidance"
|
||||
(define-assembly-routine (synchronous-trap) ()
|
||||
(inst pushf)
|
||||
(inst push rbp-tn)
|
||||
(inst mov rbp-tn rsp-tn)
|
||||
(inst and rsp-tn (- 16))
|
||||
(inst sub rsp-tn 8) ; PUSHing an odd number of GPRs
|
||||
;; Arrange in the utterly confusing order that a linux signal context has them
|
||||
;; so that we can memcpy() into a context. Push RBX twice to maintain alignment.
|
||||
(regs-pushlist rcx rax rdx rbx rbx rsi rdi r15 r14 r13 r12 r11 r10 r9 r8)
|
||||
;; ^^^ technically this is the slot for RBP
|
||||
(inst sub rsp-tn (* 16 16))
|
||||
(dotimes (i 16) (inst movdqa (ea (* i 16) rsp-tn) (sb-x86-64-asm::get-fpr :xmm i)))
|
||||
(inst lea rdi-tn (ea 24 rbp-tn)) ; stack-pointer at moment of "interrupt"
|
||||
(inst mov rsi-tn rsp-tn) ; pointer to saved CPU state
|
||||
(inst call (make-fixup "synchronous_trap" :foreign))
|
||||
(dotimes (i 16) (inst movdqa (sb-x86-64-asm::get-fpr :xmm i) (ea (* i 16) rsp-tn)))
|
||||
(inst add rsp-tn (* 16 16))
|
||||
(regs-poplist rcx rax rdx rbx rbx rsi rdi r15 r14 r13 r12 r11 r10 r9 r8)
|
||||
(inst mov rsp-tn rbp-tn)
|
||||
(inst pop rbp-tn)
|
||||
(inst popf))
|
||||
|
||||
(macrolet ((do-fprs (operation regset &aux (displacement 0))
|
||||
;; The YMM case could be removed now I suppose, since we use XSAVE + XRSTOR
|
||||
(multiple-value-bind (mnemonic fpr-align)
|
||||
|
|
|
|||
|
|
@ -310,6 +310,17 @@
|
|||
;; Just print something and go on with life.
|
||||
(setq sb-xc:*features* (remove :int4-breakpoints sb-xc:*features*))
|
||||
(warn "Removed :INT4-BREAKPOINTS from target features"))
|
||||
(when (target-featurep :x86-64)
|
||||
(let ((int3-enable (target-featurep :int3-breakpoints))
|
||||
(int4-enable (target-featurep :int4-breakpoints))
|
||||
(ud2-enable (target-featurep :ud2-breakpoints)))
|
||||
(when (or ud2-enable int4-enable)
|
||||
(setq sb-xc:*features* (remove :int3-breakpoints sb-xc:*features*))
|
||||
(when (and ud2-enable int4-enable)
|
||||
(error "UD2-BREAKPOINTS and INT4-BREAKPOINTS are mutually exclusive choices")))
|
||||
(unless (or int3-enable int4-enable ud2-enable)
|
||||
;; don't love the name, but couldn't think of a better one
|
||||
(push :sw-int-avoidance sb-xc:*features*))))
|
||||
(when (or (target-featurep :arm64)
|
||||
(and (target-featurep :x86-64)
|
||||
(member :sse4 backend-subfeatures)))
|
||||
|
|
|
|||
|
|
@ -2244,6 +2244,19 @@
|
|||
|
||||
;;;; interrupt instructions
|
||||
|
||||
;;; The default interrupt instruction is INT3 which signals SIGTRAP.
|
||||
;;; This makes for a lot of trouble when using gdb to debug lisp, because gdb really wants
|
||||
;;; to use SIGTRAP for itself. And allegedly there were OSes where SIGTRAP was unreliable
|
||||
;;; but I have never seen it, other than it being intercepted by gdb.
|
||||
;;; (Maybe that's what someone meant by "unreliable"?)
|
||||
;;; So depending on your requirement, SIGILL can be raised instead via either the INTO
|
||||
;;; instruction which is illegal on amd64, or UD2 for compabitility with 32-bit code.
|
||||
;;; UD2 is not needed on amd64 but is on 32-bit where INTO is a legal instruction.
|
||||
;;; However, if trying to debug code which also gets an "actual" SIGILL, this still poses
|
||||
;;; a problem for gdb. To workaround that we can emit a call to a asm routine which
|
||||
;;; has essentially the same effect as the signal.
|
||||
;;; Orthogonal to the preceding choices, INT1 can be used for pseudo-atomic-interrupted
|
||||
;;; but that doesn't work on all systems.
|
||||
(define-instruction break (segment &optional (code nil codep))
|
||||
(:printer byte-imm ((op #xCC)) :default :print-name 'int3 :control #'break-control)
|
||||
(:printer word-imm ((op #x0B0F)) :default :print-name 'ud2 :control #'break-control)
|
||||
|
|
@ -2251,6 +2264,11 @@
|
|||
;; use of sigtrap and shortens the error break by 1 byte relative to UD2.
|
||||
(:printer byte-imm ((op #xCE)) :default :print-name 'into :control #'break-control)
|
||||
(:emitter
|
||||
#+sw-int-avoidance ; emit CALL [EA] to skip over the trap instruction
|
||||
(let ((where (ea (make-fixup 'sb-vm::synchronous-trap :assembly-routine*))))
|
||||
(emit-prefixes segment where nil :do-not-set)
|
||||
(emit-byte segment #xFF)
|
||||
(emit-ea segment where #b010))
|
||||
#-ud2-breakpoints (emit-byte segment (or #+int4-breakpoints #xCE #xCC))
|
||||
#+ud2-breakpoints (emit-word segment #x0B0F)
|
||||
(when codep (emit-byte segment (the (unsigned-byte 8) code)))))
|
||||
|
|
|
|||
|
|
@ -267,9 +267,9 @@ resignal_to_lisp_thread(int signal, os_context_t *context)
|
|||
|
||||
/* Not safe in general, but if your thread names are all
|
||||
* simple-base-string and won't move, this is slightly ok */
|
||||
__attribute__((unused)) static char* cur_thread_name()
|
||||
char* vm_thread_name(struct thread* th)
|
||||
{
|
||||
struct thread* th = get_sb_vm_thread();
|
||||
if (!th) return "non-lisp";
|
||||
struct thread_instance *lispthread =
|
||||
(void*)(th->lisp_thread - INSTANCE_POINTER_LOWTAG);
|
||||
struct vector* name = VECTOR(lispthread->name);
|
||||
|
|
@ -375,16 +375,21 @@ sigset_tostring(const sigset_t *sigset, char* result, int result_length)
|
|||
{
|
||||
int i;
|
||||
int len = 0;
|
||||
for(i = 1; i <= MAX_SIGNUM; i++)
|
||||
if (!sigset) { strcpy(result,"nil"); return; }
|
||||
if (*(uint32_t*)sigset == 0xFFFFFFFF) { strcpy(result,"All"); return; }
|
||||
result[0] = '{';
|
||||
len = 1;
|
||||
for (i = 1; i <= MAX_SIGNUM; i++)
|
||||
if (sigismember(sigset, i)) {
|
||||
// ensure room for (generously) 3 digits + comma + null, or give up
|
||||
if (len > result_length - 5) {
|
||||
strcpy(result, "too many to list");
|
||||
return;
|
||||
}
|
||||
len += sprintf(result+len, "%s%d", len?",":"", i);
|
||||
len += sprintf(result+len, "%s%d", len>1?",":"", i);
|
||||
}
|
||||
result[len] = 0;
|
||||
result[len] = '}';
|
||||
result[len+1] = 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -211,3 +211,76 @@ os_flush_icache(os_vm_address_t __attribute__((unused)) address,
|
|||
// observable to the linker. Any one symbol suffices to resolve all of them.
|
||||
#include <math.h>
|
||||
const long libm_anchor = (long)acos;
|
||||
|
||||
#ifdef LISP_FEATURE_SW_INT_AVOIDANCE
|
||||
extern void sigtrap_handler();
|
||||
extern char* vm_thread_name(struct thread*);
|
||||
extern void sigset_tostring(const sigset_t*, char*, int);
|
||||
void synchronous_trap(lispobj* sp_at_interrupt, char* savearea)
|
||||
{
|
||||
os_context_t context;
|
||||
memset(&context, 0, sizeof context);
|
||||
|
||||
// Create the signal context from the values pushed on the stack
|
||||
// by the lisp assembly routine.
|
||||
context.uc_mcontext.fpregs = &context.__fpregs_mem;
|
||||
if (sizeof context.uc_mcontext.fpregs->_xmm[0].element != 16) lose("sigcontext size bug");
|
||||
memcpy(context.uc_mcontext.fpregs->_xmm[0].element, savearea, 16*16);
|
||||
char* gprsave = savearea + 16*16;
|
||||
memcpy(context.uc_mcontext.gregs, gprsave, 15*8);
|
||||
|
||||
context.uc_mcontext.gregs[REG_RSP] = (greg_t)sp_at_interrupt;
|
||||
// Take the return-PC to the user code which is 1 word down from exactly where
|
||||
// the stack-pointer was at the simulated INT3, then add 1 because a real INT
|
||||
// instructions leaves the PC pointing after it.
|
||||
long pc_at_interrupt = sp_at_interrupt[-1];
|
||||
context.uc_mcontext.gregs[REG_RIP] = 1 + pc_at_interrupt;
|
||||
// The first instruction of the asm routine was to push EFLAGS
|
||||
context.uc_mcontext.gregs[REG_EFL] = sp_at_interrupt[-2];
|
||||
// The next instruction was to push RBP
|
||||
context.uc_mcontext.gregs[REG_RBP] = sp_at_interrupt[-3];
|
||||
|
||||
sigset_t curmask;
|
||||
thread_sigmask(SIG_UNBLOCK, 0, &curmask); // to read the mask
|
||||
# define REAL_SIGSET_SIZE_BYTES ((NSIG/8))
|
||||
memcpy(&context.uc_sigmask, &curmask, REAL_SIGSET_SIZE_BYTES);
|
||||
thread_sigmask(SIG_BLOCK, &blockable_sigset, 0);
|
||||
sigset_t newmask;
|
||||
sigorset(&newmask, &blockable_sigset, &curmask);
|
||||
|
||||
/* char newmask_string[100];
|
||||
sigset_tostring(&newmask, newmask_string, sizeof newmask_string);
|
||||
fprintf(stderr, "[%s]: trap: pc=%lx sp=%p savearea=%p newmask=%s\n",
|
||||
vm_thread_name(get_sb_vm_thread()),
|
||||
os_context_pc(&context), sp_at_interrupt, savearea,
|
||||
newmask_string); */
|
||||
|
||||
sigtrap_handler(0, 0, &context);
|
||||
|
||||
if (context.uc_mcontext.gregs[REG_RSP] != (greg_t)sp_at_interrupt ||
|
||||
context.uc_mcontext.gregs[REG_RBP] != (greg_t)sp_at_interrupt[-3])
|
||||
lose("don't know how return to a different frame\n");
|
||||
|
||||
// Handler can alter the return PC which we need to stuff into
|
||||
// the return PC location that the assembly routine received.
|
||||
uword_t return_pc = context.uc_mcontext.gregs[REG_RIP];
|
||||
sp_at_interrupt[-1] = return_pc;
|
||||
|
||||
// act like a return-from-signal by restoring the signal mask
|
||||
// Ideally this would be performed in the asm routine only after restoring
|
||||
// registers, but it doesn't matter too much.
|
||||
thread_sigmask(SIG_SETMASK, &context.uc_sigmask, 0);
|
||||
}
|
||||
int wrapped_pthread_sigmask(int how, const void* new, void* old)
|
||||
{
|
||||
char new_string[80], old_string[80];
|
||||
sigset_tostring(new, new_string, sizeof new_string);
|
||||
int res = pthread_sigmask(how, new, old);
|
||||
sigset_tostring(old, old_string, sizeof old_string);
|
||||
fprintf(stderr, "[%s]: pthread_sigmask(%s,%s) -> %s\n",
|
||||
vm_thread_name(get_sb_vm_thread()),
|
||||
how==SIG_BLOCK?"BLOCK":how==SIG_UNBLOCK?"UNBLOCK":"SETMASK",
|
||||
new_string, old_string);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in a new issue