windows: implement exception handling

R=rsc, brainman
CC=golang-dev
https://golang.org/cl/4079041
diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c
index 09c7a1d..5eb466e 100644
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -733,6 +733,14 @@
  */
 enum
 {
+#ifdef __WINDOWS__
+	// need enough room in guard area for exception handler.
+	// use larger stacks to compensate for larger stack guard.
+	StackSmall = 256,
+	StackGuard = 2048,
+	StackBig   = 8192,
+	StackExtra = StackGuard,
+#else
 	// byte offset of stack guard (g->stackguard) above bottom of stack.
 	StackGuard = 256,
 
@@ -745,6 +753,10 @@
 	// the frame is allocated) is assumed not to be much bigger
 	// than this amount.  it may not be used efficiently if it is.
 	StackBig = 4096,
+
+	// extra room over frame size when allocating a stack.
+	StackExtra = 1024,
+#endif
 };
 
 void
@@ -812,7 +824,7 @@
 		framesize += argsize;
 		if(framesize < StackBig)
 			framesize = StackBig;
-		framesize += 1024;	// room for more functions, Stktop.
+		framesize += StackExtra;	// room for more functions, Stktop.
 		stk = runtime·stackalloc(framesize);
 		top = (Stktop*)(stk+framesize-sizeof(*top));
 		free = true;
@@ -915,7 +927,7 @@
 		if(newg->stackguard - StackGuard != newg->stack0)
 			runtime·throw("invalid stack in newg");
 	} else {
-		newg = runtime·malg(4096);
+		newg = runtime·malg(StackBig);
 		newg->status = Gwaiting;
 		newg->alllink = runtime·allg;
 		runtime·allg = newg;
diff --git a/src/pkg/runtime/windows/386/defs.h b/src/pkg/runtime/windows/386/defs.h
index f5a1636..a2a8821 100644
--- a/src/pkg/runtime/windows/386/defs.h
+++ b/src/pkg/runtime/windows/386/defs.h
@@ -10,8 +10,69 @@
 	PROT_EXEC = 0x4,
 	MAP_ANON = 0x1,
 	MAP_PRIVATE = 0x2,
+	EXCEPTION_ACCESS_VIOLATION = 0xc0000005,
+	EXCEPTION_BREAKPOINT = 0x80000003,
+	EXCEPTION_FLT_DENORMAL_OPERAND = 0xc000008d,
+	EXCEPTION_FLT_DIVIDE_BY_ZERO = 0xc000008e,
+	EXCEPTION_FLT_INEXACT_RESULT = 0xc000008f,
+	EXCEPTION_FLT_OVERFLOW = 0xc0000091,
+	EXCEPTION_FLT_UNDERFLOW = 0xc0000093,
+	EXCEPTION_INT_DIVIDE_BY_ZERO = 0xc0000094,
+	EXCEPTION_INT_OVERFLOW = 0xc0000095,
 };
 
 // Types
 #pragma pack on
+
+typedef struct ExceptionRecord ExceptionRecord;
+struct ExceptionRecord {
+	uint32 ExceptionCode;
+	uint32 ExceptionFlags;
+	ExceptionRecord *ExceptionRecord;
+	void *ExceptionAddress;
+	uint32 NumberParameters;
+	uint32 ExceptionInformation[15];
+};
+
+typedef struct FloatingSaveArea FloatingSaveArea;
+struct FloatingSaveArea {
+	uint32 ControlWord;
+	uint32 StatusWord;
+	uint32 TagWord;
+	uint32 ErrorOffset;
+	uint32 ErrorSelector;
+	uint32 DataOffset;
+	uint32 DataSelector;
+	uint8 RegisterArea[80];
+	uint32 Cr0NpxState;
+};
+
+typedef struct Context Context;
+struct Context {
+	uint32 ContextFlags;
+	uint32 Dr0;
+	uint32 Dr1;
+	uint32 Dr2;
+	uint32 Dr3;
+	uint32 Dr6;
+	uint32 Dr7;
+	FloatingSaveArea FloatSave;
+	uint32 SegGs;
+	uint32 SegFs;
+	uint32 SegEs;
+	uint32 SegDs;
+	uint32 Edi;
+	uint32 Esi;
+	uint32 Ebx;
+	uint32 Edx;
+	uint32 Ecx;
+	uint32 Eax;
+	uint32 Ebp;
+	uint32 Eip;
+	uint32 SegCs;
+	uint32 EFlags;
+	uint32 Esp;
+	uint32 SegSs;
+	uint8 ExtendedRegisters[512];
+};
 #pragma pack off
diff --git a/src/pkg/runtime/windows/386/rt0.s b/src/pkg/runtime/windows/386/rt0.s
index e379830..4b67a9f 100644
--- a/src/pkg/runtime/windows/386/rt0.s
+++ b/src/pkg/runtime/windows/386/rt0.s
@@ -3,4 +3,9 @@
 // license that can be found in the LICENSE file.
 
 TEXT _rt0_386_windows(SB),7,$0
+	// Set up SEH frame for bootstrap m
+	PUSHL	$runtime·sigtramp(SB)
+	PUSHL	0(FS)
+	MOVL	SP, 0(FS)
+
 	JMP	_rt0_386(SB)
diff --git a/src/pkg/runtime/windows/386/signal.c b/src/pkg/runtime/windows/386/signal.c
index 2ae79e5..3360252 100644
--- a/src/pkg/runtime/windows/386/signal.c
+++ b/src/pkg/runtime/windows/386/signal.c
@@ -3,6 +3,26 @@
 // license that can be found in the LICENSE file.
 
 #include "runtime.h"
+#include "defs.h"
+#include "os.h"
+
+void
+runtime·dumpregs(Context *r)
+{
+	runtime·printf("eax     %x\n", r->Eax);
+	runtime·printf("ebx     %x\n", r->Ebx);
+	runtime·printf("ecx     %x\n", r->Ecx);
+	runtime·printf("edx     %x\n", r->Edx);
+	runtime·printf("edi     %x\n", r->Edi);
+	runtime·printf("esi     %x\n", r->Esi);
+	runtime·printf("ebp     %x\n", r->Ebp);
+	runtime·printf("esp     %x\n", r->Esp);
+	runtime·printf("eip     %x\n", r->Eip);
+	runtime·printf("eflags  %x\n", r->EFlags);
+	runtime·printf("cs      %x\n", r->SegCs);
+	runtime·printf("fs      %x\n", r->SegFs);
+	runtime·printf("gs      %x\n", r->SegGs);
+}
 
 void
 runtime·initsig(int32)
@@ -15,3 +35,61 @@
 	return runtime·emptystring;
 }
 
+uint32
+runtime·sighandler(ExceptionRecord *info, void *frame, Context *r)
+{
+	uintptr *sp;
+	G *gp;
+
+	USED(frame);
+
+	switch(info->ExceptionCode) {
+	case EXCEPTION_BREAKPOINT:
+		r->Eip--;	// because 8l generates 2 bytes for INT3
+		return 1;
+	}
+
+	if((gp = m->curg) != nil && runtime·issigpanic(info->ExceptionCode)) {
+		// Make it look like a call to the signal func.
+		// Have to pass arguments out of band since
+		// augmenting the stack frame would break
+		// the unwinding code.
+		gp->sig = info->ExceptionCode;
+		gp->sigcode0 = info->ExceptionInformation[0];
+		gp->sigcode1 = info->ExceptionInformation[1];
+
+		// Only push runtime·sigpanic if r->eip != 0.
+		// If r->eip == 0, probably panicked because of a
+		// call to a nil func.  Not pushing that onto sp will
+		// make the trace look like a call to runtime·sigpanic instead.
+		// (Otherwise the trace will end at runtime·sigpanic and we
+		// won't get to see who faulted.)
+		if(r->Eip != 0) {
+			sp = (uintptr*)r->Esp;
+			*--sp = r->Eip;
+			r->Esp = (uintptr)sp;
+		}
+		r->Eip = (uintptr)runtime·sigpanic;
+		return 0;
+	}
+
+	if(runtime·panicking)	// traceback already printed
+		runtime·exit(2);
+	runtime·panicking = 1;
+
+	runtime·printf("Exception %x %p %p\n", info->ExceptionCode,
+		info->ExceptionInformation[0], info->ExceptionInformation[1]);
+
+	runtime·printf("PC=%x\n", r->Eip);
+	runtime·printf("\n");
+
+	if(runtime·gotraceback()){
+		runtime·traceback((void*)r->Eip, (void*)r->Esp, 0, m->curg);
+		runtime·tracebackothers(m->curg);
+		runtime·dumpregs(r);
+	}
+
+	runtime·breakpoint();
+	runtime·exit(2);
+	return 0;
+}
diff --git a/src/pkg/runtime/windows/386/sys.s b/src/pkg/runtime/windows/386/sys.s
index 7f99b34..b3abab5 100644
--- a/src/pkg/runtime/windows/386/sys.s
+++ b/src/pkg/runtime/windows/386/sys.s
@@ -48,12 +48,58 @@
 
 	RET 
 
+TEXT runtime·sigtramp(SB),7,$0
+	PUSHL	BP					// cdecl
+	PUSHL	0(FS)
+	CALL	runtime·sigtramp1(SB)
+	POPL	0(FS)
+	POPL	BP
+	RET
+
+TEXT runtime·sigtramp1(SB),0,$16-28
+	// unwinding?
+	MOVL	info+12(FP), BX
+	MOVL	4(BX), CX			// exception flags
+	ANDL	$6, CX
+	MOVL	$1, AX
+	JNZ		sigdone
+
+	// place ourselves at the top of the SEH chain to
+	// ensure SEH frames lie within thread stack bounds
+	MOVL	frame+16(FP), CX	// our SEH frame
+	MOVL	CX, 0(FS)
+
+	// copy arguments for call to sighandler
+	MOVL	BX, 0(SP)
+	MOVL	CX, 4(SP)
+	MOVL	context+20(FP), BX
+	MOVL	BX, 8(SP)
+	MOVL	dispatcher+24(FP), BX
+	MOVL	BX, 12(SP)
+
+	CALL	runtime·sighandler(SB)
+	TESTL	AX, AX
+	JZ		sigdone
+
+	// call windows default handler early
+	MOVL	4(SP), BX			// our SEH frame
+	MOVL	0(BX), BX			// SEH frame of default handler
+	MOVL	4(BX), AX			// handler function pointer
+	MOVL	BX, 4(SP)			// set establisher frame
+	CALL	AX
+
+sigdone:
+	RET
+
 // void tstart(M *newm);
 TEXT runtime·tstart(SB),7,$0
 	MOVL	newm+4(SP), CX		// m
 	MOVL	m_g0(CX), DX		// g
 
-	MOVL	SP, DI			// remember stack
+	// Set up SEH frame
+	PUSHL	$runtime·sigtramp(SB)
+	PUSHL	0(FS)
+	MOVL	SP, 0(FS)
 
 	// Layout new m scheduler stack on os stack.
 	MOVL	SP, AX
@@ -74,14 +120,14 @@
 	// Someday the convention will be D is always cleared.
 	CLD
 
-	PUSHL	DI			// original stack
-
 	CALL	runtime·stackcheck(SB)		// clobbers AX,CX
 
 	CALL	runtime·mstart(SB)
 
-	POPL	DI			// original stack
-	MOVL	DI, SP
+	// Pop SEH frame
+	MOVL	0(FS), SP
+	POPL	0(FS)
+	POPL	CX
 
 	RET
 
diff --git a/src/pkg/runtime/windows/defs.c b/src/pkg/runtime/windows/defs.c
index db5f140..5aac03c 100644
--- a/src/pkg/runtime/windows/defs.c
+++ b/src/pkg/runtime/windows/defs.c
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+#include <stdarg.h>
+#include <windef.h>
+#include <winbase.h>
+
 enum {
 	$PROT_NONE = 0,
 	$PROT_READ = 1,
@@ -10,4 +14,18 @@
 
 	$MAP_ANON = 1,
 	$MAP_PRIVATE = 2,
+
+	$EXCEPTION_ACCESS_VIOLATION = STATUS_ACCESS_VIOLATION,
+	$EXCEPTION_BREAKPOINT = STATUS_BREAKPOINT,
+	$EXCEPTION_FLT_DENORMAL_OPERAND = STATUS_FLOAT_DENORMAL_OPERAND,
+	$EXCEPTION_FLT_DIVIDE_BY_ZERO = STATUS_FLOAT_DIVIDE_BY_ZERO,
+	$EXCEPTION_FLT_INEXACT_RESULT = STATUS_FLOAT_INEXACT_RESULT,
+	$EXCEPTION_FLT_OVERFLOW = STATUS_FLOAT_OVERFLOW,
+	$EXCEPTION_FLT_UNDERFLOW = STATUS_FLOAT_UNDERFLOW,
+	$EXCEPTION_INT_DIVIDE_BY_ZERO = STATUS_INTEGER_DIVIDE_BY_ZERO,
+	$EXCEPTION_INT_OVERFLOW = STATUS_INTEGER_OVERFLOW,
 };
+
+typedef EXCEPTION_RECORD $ExceptionRecord;
+typedef FLOATING_SAVE_AREA $FloatingSaveArea;
+typedef CONTEXT $Context;
diff --git a/src/pkg/runtime/windows/mem.c b/src/pkg/runtime/windows/mem.c
index ba89887..c42bf9f 100644
--- a/src/pkg/runtime/windows/mem.c
+++ b/src/pkg/runtime/windows/mem.c
@@ -53,6 +53,7 @@
 {
 	uintptr r;
 
+	USED(n);
 	r = (uintptr)runtime·stdcall(runtime·VirtualFree, 3, v, 0, MEM_RELEASE);
 	if(r == 0)
 		abort("VirtualFree");
diff --git a/src/pkg/runtime/windows/os.h b/src/pkg/runtime/windows/os.h
index 77d0d32..23f6863 100644
--- a/src/pkg/runtime/windows/os.h
+++ b/src/pkg/runtime/windows/os.h
@@ -34,3 +34,5 @@
 };
 
 void runtime·syscall(StdcallParams *p);
+uint32 runtime·issigpanic(uint32);
+void runtime·sigpanic(void);
diff --git a/src/pkg/runtime/windows/thread.c b/src/pkg/runtime/windows/thread.c
index 9b51813..5ab5128 100644
--- a/src/pkg/runtime/windows/thread.c
+++ b/src/pkg/runtime/windows/thread.c
@@ -3,6 +3,7 @@
 // license that can be found in the LICENSE file.
 
 #include "runtime.h"
+#include "defs.h"
 #include "os.h"
 
 #pragma dynimport runtime·LoadLibraryEx LoadLibraryExA "kernel32.dll"
@@ -215,3 +216,43 @@
 	p->err = (uintptr)runtime·stdcall_raw(runtime·GetLastError, 0, &a);
 	runtime·exitsyscall();
 }
+
+uint32
+runtime·issigpanic(uint32 code)
+{
+	switch(code) {
+	case EXCEPTION_ACCESS_VIOLATION:
+	case EXCEPTION_INT_DIVIDE_BY_ZERO:
+	case EXCEPTION_INT_OVERFLOW:
+	case EXCEPTION_FLT_DENORMAL_OPERAND:
+	case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+	case EXCEPTION_FLT_INEXACT_RESULT:
+	case EXCEPTION_FLT_OVERFLOW:
+	case EXCEPTION_FLT_UNDERFLOW:
+		return 1;
+	}
+	return 0;
+}
+
+void
+runtime·sigpanic(void)
+{
+	switch(g->sig) {
+	case EXCEPTION_ACCESS_VIOLATION:
+		if(g->sigcode1 < 0x1000)
+			runtime·panicstring("invalid memory address or nil pointer dereference");
+		runtime·printf("unexpected fault address %p\n", g->sigcode1);
+		runtime·throw("fault");
+	case EXCEPTION_INT_DIVIDE_BY_ZERO:
+		runtime·panicstring("integer divide by zero");
+	case EXCEPTION_INT_OVERFLOW:
+		runtime·panicstring("integer overflow");
+	case EXCEPTION_FLT_DENORMAL_OPERAND:
+	case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+	case EXCEPTION_FLT_INEXACT_RESULT:
+	case EXCEPTION_FLT_OVERFLOW:
+	case EXCEPTION_FLT_UNDERFLOW:
+		runtime·panicstring("floating point error");
+	}
+	runtime·throw("fault");
+}