xorl %eax, %eax

OpenBSD i386 XMM Unhandled Exception

leave a comment »

This bug was reported by Slava Pestov at openbsd-bugs mailing list. The problem was that a simple SSE2 exception was leading to instant kernel panic on OpenBSD 4.5. Brynet quickly noticed that it only affects i386 systems with SSE2 support and in his next email to the list, he made a funny discovery…
OpenBSD/i386 did not have a handler routine for XMM exceptions. So, let’s have a look at src/sys/arch/i386/i386/trap.c of OpenBSD 4.6…

/*
 * trap(frame):
 *	Exception, fault, and trap interface to BSD kernel. This
 * common code is called from assembly language IDT gate entry
 * routines that prepare a suitable stack frame, and restore this
 * frame after the exception has been processed. Note that the
 * effect is as if the arguments were passed call by reference.
 */
/*ARGSUSED*/
void
trap(struct trapframe frame)
{
	struct proc *p = curproc;
	int type = frame.tf_trapno;
   ...
	switch (type) {

	/* trace trap */
	case T_TRCTRAP: {
   ...
	}
	/* FALLTHROUGH */

	default:
	we_re_toast:
#ifdef KGDB
   ...
#endif
		if (frame.tf_trapno < trap_types)
			printf("fatal %s (%d)", trap_type[frame.tf_trapno],
				frame.tf_trapno);
		else
			printf("unknown trap %d", frame.tf_trapno);
		printf(" in %s mode\n", (type & T_USER) ? "user" : "supervisor");
		printf("trap type %d code %x eip %x cs %x eflags %x cr2 %x cpl %x\n",
		    type, frame.tf_err, frame.tf_eip, frame.tf_cs, frame.tf_eflags, rcr2(), lapic_tpr);

		panic("trap type %d, code=%x, pc=%x",
		    type, frame.tf_err, frame.tf_eip);
		/*NOTREACHED*/

	case T_PROTFLT:
   ...
	case T_SEGNPFLT:
	case T_ALIGNFLT:
   ...
	case T_PROTFLT|T_USER:		/* protection fault */
   ...
	case T_TSSFLT|T_USER:
   ...
	case T_SEGNPFLT|T_USER:
	case T_STKFLT|T_USER:
   ...
	case T_ALIGNFLT|T_USER:
   ...
	case T_PRIVINFLT|T_USER:	/* privileged instruction fault */
   ...
	case T_FPOPFLT|T_USER:		/* coprocessor operand fault */
   ...
	case T_ASTFLT|T_USER:		/* Allow process switch */
   ...
	case T_DNA|T_USER: {
   ...
	case T_BOUND|T_USER:
   ...
	case T_OFLOW|T_USER:
   ...
	case T_DIVIDE|T_USER:
   ...
	case T_ARITHTRAP|T_USER:
   ...
	case T_PAGEFLT:			/* allow page faults in kernel mode */
   ...
	case T_PAGEFLT|T_USER: {	/* page fault */
   ...
	case T_BPTFLT|T_USER:		/* bpt instruction fault */
   ...
	case T_TRCTRAP|T_USER:		/* trace trap */
   ...
	case T_NMI:
	case T_NMI|T_USER:
   ...
}

So, since there is no ‘T_XFTRAP’ (SIMD Floating Point Fault) case, every XMM exception will result in calling the default case which leads to a simple panic(). This was patched by adding the missing handler like this:

+		goto out;
+
+	case T_XFTRAP|T_USER:
+		npxtrap(&frame);
 		goto out;
 
 	case T_PAGEFLT:			/* allow page faults in kernel mode */

This simple case will call npxtrap() which is a new routine added in src/sys/arch/i386/isa/npx.c:

void
npxtrap(struct trapframe *frame)
{
	struct proc *p = curcpu()->ci_fpcurproc;
	union savefpu *addr = &p->p_addr->u_pcb.pcb_savefpu;
	u_int32_t mxcsr, statbits;
	int code;
	union sigval sv;

#ifdef DIAGNOSTIC
	/*
	 * At this point, fpcurproc should be curproc.  If it wasn't, the TS
	 * bit should be set, and we should have gotten a DNA exception.
	 */
	if (p != curproc)
		panic("npxtrap: wrong process");
#endif

	fxsave(&addr->sv_xmm);
	mxcsr = addr->sv_xmm.sv_env.en_mxcsr;
	statbits = mxcsr;
	mxcsr &= ~0x3f;
	ldmxcsr(&mxcsr);
	addr->sv_xmm.sv_ex_sw = addr->sv_xmm.sv_env.en_sw;
	addr->sv_xmm.sv_ex_tw = addr->sv_xmm.sv_env.en_tw;
	code = x86fpflags_to_siginfo (statbits);
	sv.sival_int = frame->tf_eip;
	KERNEL_PROC_LOCK(p);
	trapsignal(p, SIGFPE, frame->tf_err, code, sv);
	KERNEL_PROC_UNLOCK(p);
}

The new macros it uses where also added in sys/arch/i386/isa/npx.c:

-#define        fxsave(addr)            __asm("fxsave %0" : "=m" (*addr))
-#define        fxrstor(addr)           __asm("fxrstor %0" : : "m" (*addr))
+#define fxsave(addr)		__asm("fxsave %0" : "=m" (*addr))
+#define fxrstor(addr)		__asm("fxrstor %0" : : "m" (*addr))
+#define ldmxcsr(addr)		__asm("ldmxcsr %0" : : "m" (*addr))

The aim of npxtrap() is to save the current state of x87 FPU to ‘&addr->sv_xmm’, then store the contents of ‘mxcsr’ (Multimedia eXtensions Control and Status Register) to ‘statbits’ and clear all the exception flags using ‘~0x3f’. After clearing the exception flags, it uses ldmxcsr() to load the updated MXCSR register and the subsequent assignments are updating the XMM Exception Status Word (addr->sv_xmm.sv_ex_sw) with the current state (addr->sv_xmm.sv_env.en_sw) and the XMM Exception Tag Word (addr->sv_xmm.sv_ex_tw) with the current one (addr->sv_xmm.sv_env.en_tw). Finally, x86fpflags_to_siginfo() is used to update the ‘x86fp_siginfo_table[]’ array and at last, trapsignal() to send a SIGFPE (Floating Point Exception Signal) to the process that raised the exception.
The trigger code for that bug is fairly simple…

#include <xmmintrin.h>
#include <stdio.h>

int main()
{
       _MM_SET_EXCEPTION_MASK(_MM_GET_EXCEPTION_MASK() & ~(_MM_MASK_DIV_ZERO));
       double a = 1.0;
       double b = 0.0;
       __asm__("divsd %1, %0" : "+x" (a) : "x" (b));
       printf("%f\n",a);
       return 0;
}

He uses _MM_SET_EXCEPTION_MASK() macro to update the current exception mask (retrieved with _MM_GET_EXCEPTION_MASK()) to clear the “Divide By Zero” flag (using ~(_MM_MASK_DIV_ZERO)) and then, he simply performs a division of ‘a’ which is 1.0 with ‘b’ which is 0.0 using divsd SSE2 instruction.

Written by xorl

October 16, 2009 at 19:47

Posted in bugs, openbsd

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s