Commit 3750904a authored by Russ Cox's avatar Russ Cox

runtime: use VEH, not SEH, for windows/386 exception handling

Structured Exception Handling (SEH) was the first way to handle
exceptions (memory faults, divides by zero) on Windows.
The S might as well stand for "stack-based": the implementation
interprets stack addresses in a few different ways, and it gets
subtly confused by Go's management of stacks. It's also something
that requires active maintenance during cgo switches, and we've
had bugs in that maintenance in the past.

We have recently come to believe that SEH cannot work with
Go's stack usage. See http://golang.org/issue/7325 for details.

Vectored Exception Handling (VEH) is more like a Unix signal
handler: you set it once for the whole process and forget about it.

This CL drops all the SEH code and replaces it with VEH code.
Many special cases and 7 #ifdefs disappear.

VEH was introduced in Windows XP, so Go on windows/386 will
now require Windows XP or later. The previous requirement was
Windows 2000 or later. Windows 2000 immediately preceded
Windows XP, so Windows 2000 is the only affected version.
Microsoft stopped supporting Windows 2000 in 2010.
See http://golang.org/s/win2000-golang-nuts for details.

Fixes #7325.

LGTM=alex.brainman, r
R=golang-codereviews, alex.brainman, stephen.gutekanst, dave
CC=golang-codereviews, iant, r
https://golang.org/cl/74790043
parent 3b27343c
......@@ -657,7 +657,6 @@ havem:
// Save current sp in m->g0->sched.sp in preparation for
// switch back to m->curg stack.
// NOTE: unwindm knows that the saved g->sched.sp is at 0(SP).
// On Windows, the SEH is at 4(SP) and 8(SP).
MOVL m_g0(BP), SI
MOVL (g_sched+gobuf_sp)(SI), AX
MOVL AX, 0(SP)
......
......@@ -98,7 +98,6 @@ void
runtime·cgocall(void (*fn)(void*), void *arg)
{
Defer d;
SEHUnwind sehunwind;
if(!runtime·iscgo && !Solaris && !Windows)
runtime·throw("cgocall unavailable");
......@@ -127,14 +126,6 @@ runtime·cgocall(void (*fn)(void*), void *arg)
d.special = true;
g->defer = &d;
// Record current SEH for restoration during endcgo.
// This matters most when the execution stops due to panic
// and the called C code isn't given a chance to clean up
// the SEHs it has pushed.
sehunwind.seh = runtime·getseh();
sehunwind.link = m->sehunwind;
m->sehunwind = &sehunwind;
m->ncgo++;
/*
......@@ -170,9 +161,6 @@ endcgo(void)
m->cgomal = nil;
}
runtime·setseh(m->sehunwind->seh);
m->sehunwind = m->sehunwind->link;
if(raceenabled)
runtime·raceacquire(&cgosync);
}
......
......@@ -90,7 +90,3 @@ var MemclrBytes = memclrBytes
func gogoBytes() int32
var GogoBytes = gogoBytes
func getseh_go() uintptr
var GetSEH = getseh_go
......@@ -8,6 +8,7 @@
#include "os_GOOS.h"
#include "../../cmd/ld/textflag.h"
#pragma dynimport runtime·AddVectoredExceptionHandler AddVectoredExceptionHandler "kernel32.dll"
#pragma dynimport runtime·CloseHandle CloseHandle "kernel32.dll"
#pragma dynimport runtime·CreateEvent CreateEventA "kernel32.dll"
#pragma dynimport runtime·CreateThread CreateThread "kernel32.dll"
......@@ -20,26 +21,27 @@
#pragma dynimport runtime·FreeEnvironmentStringsW FreeEnvironmentStringsW "kernel32.dll"
#pragma dynimport runtime·GetEnvironmentStringsW GetEnvironmentStringsW "kernel32.dll"
#pragma dynimport runtime·GetProcAddress GetProcAddress "kernel32.dll"
#pragma dynimport runtime·GetQueuedCompletionStatusEx GetQueuedCompletionStatusEx "kernel32.dll"
#pragma dynimport runtime·GetStdHandle GetStdHandle "kernel32.dll"
#pragma dynimport runtime·GetSystemInfo GetSystemInfo "kernel32.dll"
#pragma dynimport runtime·GetSystemTimeAsFileTime GetSystemTimeAsFileTime "kernel32.dll"
#pragma dynimport runtime·GetThreadContext GetThreadContext "kernel32.dll"
#pragma dynimport runtime·LoadLibrary LoadLibraryW "kernel32.dll"
#pragma dynimport runtime·LoadLibraryA LoadLibraryA "kernel32.dll"
#pragma dynimport runtime·NtWaitForSingleObject NtWaitForSingleObject "ntdll.dll"
#pragma dynimport runtime·ResumeThread ResumeThread "kernel32.dll"
#pragma dynimport runtime·SetConsoleCtrlHandler SetConsoleCtrlHandler "kernel32.dll"
#pragma dynimport runtime·SetEvent SetEvent "kernel32.dll"
#pragma dynimport runtime·SetProcessPriorityBoost SetProcessPriorityBoost "kernel32.dll"
#pragma dynimport runtime·SetThreadPriority SetThreadPriority "kernel32.dll"
#pragma dynimport runtime·SetWaitableTimer SetWaitableTimer "kernel32.dll"
#pragma dynimport runtime·Sleep Sleep "kernel32.dll"
#pragma dynimport runtime·SuspendThread SuspendThread "kernel32.dll"
#pragma dynimport runtime·timeBeginPeriod timeBeginPeriod "winmm.dll"
#pragma dynimport runtime·WaitForSingleObject WaitForSingleObject "kernel32.dll"
#pragma dynimport runtime·WriteFile WriteFile "kernel32.dll"
#pragma dynimport runtime·NtWaitForSingleObject NtWaitForSingleObject "ntdll.dll"
extern void *runtime·NtWaitForSingleObject;
#pragma dynimport runtime·timeBeginPeriod timeBeginPeriod "winmm.dll"
extern void *runtime·AddVectoredExceptionHandler;
extern void *runtime·CloseHandle;
extern void *runtime·CreateEvent;
extern void *runtime·CreateThread;
......@@ -52,27 +54,29 @@ extern void *runtime·ExitProcess;
extern void *runtime·FreeEnvironmentStringsW;
extern void *runtime·GetEnvironmentStringsW;
extern void *runtime·GetProcAddress;
extern void *runtime·GetQueuedCompletionStatusEx;
extern void *runtime·GetStdHandle;
extern void *runtime·GetSystemInfo;
extern void *runtime·GetSystemTimeAsFileTime;
extern void *runtime·GetThreadContext;
extern void *runtime·LoadLibrary;
extern void *runtime·LoadLibraryA;
extern void *runtime·NtWaitForSingleObject;
extern void *runtime·ResumeThread;
extern void *runtime·SetConsoleCtrlHandler;
extern void *runtime·SetEvent;
extern void *runtime·SetProcessPriorityBoost;
extern void *runtime·SetThreadPriority;
extern void *runtime·SetWaitableTimer;
extern void *runtime·Sleep;
extern void *runtime·SuspendThread;
extern void *runtime·timeBeginPeriod;
extern void *runtime·WaitForSingleObject;
extern void *runtime·WriteFile;
void *runtime·GetQueuedCompletionStatusEx;
extern void *runtime·timeBeginPeriod;
extern uintptr runtime·externalthreadhandlerp;
void runtime·externalthreadhandler(void);
void runtime·sigtramp(void);
static int32
getproccount(void)
......@@ -86,26 +90,18 @@ getproccount(void)
void
runtime·osinit(void)
{
void *kernel32;
void *SetProcessPriorityBoost;
runtime·externalthreadhandlerp = (uintptr)runtime·externalthreadhandler;
runtime·stdcall(runtime·AddVectoredExceptionHandler, 2, (uintptr)1, (uintptr)runtime·sigtramp);
runtime·stdcall(runtime·SetConsoleCtrlHandler, 2, runtime·ctrlhandler, (uintptr)1);
runtime·stdcall(runtime·timeBeginPeriod, 1, (uintptr)1);
runtime·ncpu = getproccount();
kernel32 = runtime·stdcall(runtime·LoadLibraryA, 1, "kernel32.dll");
if(kernel32 != nil) {
// Windows dynamic priority boosting assumes that a process has different types
// of dedicated threads -- GUI, IO, computational, etc. Go processes use
// equivalent threads that all do a mix of GUI, IO, computations, etc.
// In such context dynamic priority boosting does nothing but harm, so we turn it off.
SetProcessPriorityBoost = runtime·stdcall(runtime·GetProcAddress, 2, kernel32, "SetProcessPriorityBoost");
if(SetProcessPriorityBoost != nil) // supported since Windows XP
runtime·stdcall(SetProcessPriorityBoost, 2, (uintptr)-1, (uintptr)1);
runtime·GetQueuedCompletionStatusEx = runtime·stdcall(runtime·GetProcAddress, 2, kernel32, "GetQueuedCompletionStatusEx");
}
runtime·stdcall(runtime·SetProcessPriorityBoost, 2, (uintptr)-1, (uintptr)1);
}
void
......@@ -369,8 +365,6 @@ runtime·sigpanic(void)
runtime·throw("fault");
}
extern void *runtime·sigtramp;
void
runtime·initsig(void)
{
......
......@@ -24,19 +24,41 @@ runtime·dumpregs(Context *r)
runtime·printf("gs %x\n", r->SegGs);
}
#define DBG_PRINTEXCEPTION_C 0x40010006
// Called by sigtramp from Windows VEH handler.
// Return value signals whether the exception has been handled (-1)
// or should be made available to other handlers in the chain (0).
uint32
runtime·sighandler(ExceptionRecord *info, Context *r, G *gp)
{
bool crash;
uintptr *sp;
/*
switch(info->ExceptionCode) {
case DBG_PRINTEXCEPTION_C:
// This exception is intended to be caught by debuggers.
// There is a not-very-informational message like
// "Invalid parameter passed to C runtime function"
// sitting at info->ExceptionInformation[0] (a wchar_t*),
// with length info->ExceptionInformation[1].
// The default behavior is to ignore this exception,
// but somehow returning 0 here (meaning keep going)
// makes the program crash instead. Maybe Windows has no
// other handler registered? In any event, ignore it.
return -1;
case EXCEPTION_BREAKPOINT:
r->Eip--; // because 8l generates 2 bytes for INT3
return 1;
// It is unclear whether this is needed, unclear whether it
// would work, and unclear how to test it. Leave out for now.
// This only handles breakpoint instructions written in the
// assembly sources, not breakpoints set by a debugger, and
// there are very few of the former.
//
// r->Eip--; // because 8l generates 2 bytes for INT3
// return 0;
break;
}
*/
if(gp != nil && runtime·issigpanic(info->ExceptionCode)) {
// Make it look like a call to the signal func.
......@@ -60,15 +82,15 @@ runtime·sighandler(ExceptionRecord *info, Context *r, G *gp)
r->Esp = (uintptr)sp;
}
r->Eip = (uintptr)runtime·sigpanic;
return 0;
return -1;
}
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("Exception %x %p %p %p\n", info->ExceptionCode,
info->ExceptionInformation[0], info->ExceptionInformation[1], r->Eip);
runtime·printf("PC=%x\n", r->Eip);
if(m->lockedg != nil && m->ncgo > 0 && gp == m->g0) {
......@@ -86,9 +108,8 @@ runtime·sighandler(ExceptionRecord *info, Context *r, G *gp)
if(crash)
runtime·crash();
runtime·exit(2);
return 0;
return -1; // not reached
}
void
......
......@@ -599,13 +599,6 @@ runtime·starttheworld(void)
void
runtime·mstart(void)
{
#ifdef GOOSARCH_windows_386
// It is used by windows-386 only. Unfortunately, seh needs
// to be located on os stack, and mstart runs on os stack
// for both m0 and m.
SEH seh;
#endif
if(g != m->g0)
runtime·throw("bad runtime·mstart");
......@@ -615,9 +608,6 @@ runtime·mstart(void)
runtime·gosave(&m->g0->sched);
m->g0->sched.pc = (uintptr)-1; // make sure it is never used
m->g0->stackguard = m->g0->stackguard0; // cgo sets only stackguard0, copy it to stackguard
#ifdef GOOSARCH_windows_386
m->seh = &seh;
#endif
runtime·asminit();
runtime·minit();
......@@ -769,14 +759,6 @@ runtime·needm(byte x)
g->stackguard = (uintptr)(&x - 32*1024);
g->stackguard0 = g->stackguard;
#ifdef GOOSARCH_windows_386
// On windows/386, we need to put an SEH frame (two words)
// somewhere on the current stack. We are called from cgocallback_gofunc
// and we know that it will leave two unused words below m->curg->sched.sp.
// Use those.
m->seh = (SEH*)((uintptr*)&x + 1);
#endif
// Initialize this thread to use the m.
runtime·asminit();
runtime·minit();
......@@ -854,10 +836,6 @@ runtime·dropm(void)
// Undo whatever initialization minit did during needm.
runtime·unminit();
#ifdef GOOSARCH_windows_386
m->seh = nil; // reset dangling typed pointer
#endif
// Clear m and g, and return m to the extra list.
// After the call to setmg we can only call nosplit functions.
mp = m;
......
......@@ -83,8 +83,6 @@ typedef struct Hchan Hchan;
typedef struct Complex64 Complex64;
typedef struct Complex128 Complex128;
typedef struct LibCall LibCall;
typedef struct SEH SEH;
typedef struct SEHUnwind SEHUnwind;
typedef struct WinCallbackContext WinCallbackContext;
typedef struct Timers Timers;
typedef struct Timer Timer;
......@@ -241,18 +239,6 @@ struct LibCall
uintptr err; // error number
};
struct SEH
{
void* prev;
void* handler;
};
struct SEHUnwind
{
SEHUnwind* link;
SEH* seh;
};
// describes how to handle callback
struct WinCallbackContext
{
......@@ -303,15 +289,6 @@ struct G
uintptr end[];
};
// Define a symbol for windows/386 because that is the only
// system with SEH handling, and we end up checking that
// repeatedly.
#ifdef GOOS_windows
#ifdef GOARCH_386
#define GOOSARCH_windows_386
#endif
#endif
struct M
{
G* g0; // goroutine with scheduling stack
......@@ -394,8 +371,6 @@ struct M
int8* notesig;
byte* errstr;
#endif
SEH* seh;
SEHUnwind* sehunwind;
uintptr end[];
};
......@@ -975,16 +950,6 @@ void* runtime·funcdata(Func*, int32);
int32 runtime·setmaxthreads(int32);
G* runtime·timejump(void);
// On Windows 386, we have functions for saving and restoring
// the SEH values; elsewhere #define them away.
#ifdef GOOSARCH_windows_386
SEH* runtime·getseh(void);
void runtime·setseh(SEH*);
#else
#define runtime·getseh() nil
#define runtime·setseh(x) do{}while(0)
#endif
#pragma varargck argpos runtime·printf 1
#pragma varargck type "c" int32
#pragma varargck type "d" int32
......
......@@ -43,10 +43,6 @@ func gogoBytes() (x int32) {
x = RuntimeGogoBytes;
}
func getseh_go() (x uintptr) {
x = (uintptr)runtime·getseh();
}
func typestring(e Eface) (s String) {
s = *e.type->string;
}
......
......@@ -69,43 +69,66 @@ TEXT runtime·setlasterror(SB),NOSPLIT,$0
MOVL AX, 0x34(FS)
RET
TEXT runtime·sigtramp(SB),NOSPLIT,$28
// unwinding?
MOVL info+0(FP), CX
TESTL $6, 4(CX) // exception flags
MOVL $1, AX
JNZ sigdone
// copy arguments for call to sighandler
MOVL CX, 0(SP)
MOVL context+8(FP), CX
MOVL CX, 4(SP)
get_tls(CX)
// check that m exists
MOVL m(CX), AX
CMPL AX, $0
JNE 2(PC)
CALL runtime·badsignal2(SB)
MOVL g(CX), CX
MOVL CX, 8(SP)
// Called by Windows as a Vectored Exception Handler (VEH).
// First argument is pointer to struct containing
// exception record and context pointers.
// Return 0 for 'not handled', -1 for handled.
TEXT runtime·sigtramp(SB),NOSPLIT,$0-0
MOVL ptrs+0(FP), DI
SUBL $28, SP
MOVL 0(DI), BX // ExceptionRecord*
MOVL 4(DI), CX // Context*
// Only handle exception if executing instructions in Go binary
// (not Windows library code). Except don't - keep reading.
//
// This sounds like a good idea but the tracebacks that
// Go provides are better than the Windows crash dialog,
// especially if it's something that Go needs to do.
// So take all the exceptions, not just the ones at Go PCs.
// If you re-enable this check by removing the JMP, you will
// need to arrange to handle exception 0x40010006 during
// non-Go code here. Right now that case is handled by sighandler
// in os_windows_386.c.
JMP skipcheckpc
MOVL $0, AX
MOVL 184(CX), DX // saved PC
CMPL DX, $text(SB)
JB vehret
CMPL DX, $etext(SB)
JA vehret
skipcheckpc:
// save callee-saved registers
MOVL BX, 12(SP)
MOVL BP, 16(SP)
MOVL SI, 20(SP)
MOVL DI, 24(SP)
// fetch g
get_tls(DX)
MOVL m(DX), AX
CMPL AX, $0
JNE 2(PC)
CALL runtime·badsignal2(SB)
MOVL g(DX), DX
// call sighandler(ExceptionRecord*, Context*, G*)
MOVL BX, 0(SP)
MOVL CX, 4(SP)
MOVL DX, 8(SP)
CALL runtime·sighandler(SB)
// AX is set to report result back to Windows
// restore callee-saved registers
MOVL 24(SP), DI
MOVL 20(SP), SI
MOVL 16(SP), BP
MOVL 12(SP), BX
sigdone:
RET
vehret:
ADDL $28, SP
// RET 4 (return and pop 4 bytes parameters)
BYTE $0xC2; WORD $4
RET // unreached; make assembler happy
TEXT runtime·ctrlhandler(SB),NOSPLIT,$0
PUSHL $runtime·ctrlhandler1(SB)
......@@ -182,11 +205,6 @@ TEXT runtime·callbackasm1+0(SB),NOSPLIT,$0
PUSHL BP
PUSHL BX
// set up SEH frame again
PUSHL $runtime·sigtramp(SB)
PUSHL 0(FS)
MOVL SP, 0(FS)
// determine index into runtime·cbctxts table
SUBL $runtime·callbackasm(SB), AX
MOVL $0, DX
......@@ -232,10 +250,6 @@ TEXT runtime·callbackasm1+0(SB),NOSPLIT,$0
MOVL BX, CX // cannot use BX anymore
// pop SEH frame
POPL 0(FS)
POPL BX
// restore registers as required for windows callback
POPL BX
POPL BP
......@@ -301,31 +315,10 @@ TEXT runtime·setldt(SB),NOSPLIT,$0
// void install_exception_handler()
TEXT runtime·install_exception_handler(SB),NOSPLIT,$0
get_tls(CX)
MOVL m(CX), CX // m
// Set up SEH frame
MOVL m_seh(CX), DX
MOVL $runtime·sigtramp(SB), AX
MOVL AX, seh_handler(DX)
MOVL 0(FS), AX
MOVL AX, seh_prev(DX)
// Install it
MOVL DX, 0(FS)
RET
// void remove_exception_handler()
TEXT runtime·remove_exception_handler(SB),NOSPLIT,$0
get_tls(CX)
MOVL m(CX), CX // m
// Remove SEH frame
MOVL m_seh(CX), DX
MOVL seh_prev(DX), AX
MOVL AX, 0(FS)
RET
// Sleep duration is in 100ns units.
......@@ -390,13 +383,3 @@ TEXT runtime·usleep2(SB),NOSPLIT,$20
CALL AX
MOVL BP, SP
RET
TEXT runtime·getseh(SB),NOSPLIT,$0
MOVL 0(FS), AX
RET
TEXT runtime·setseh(SB),NOSPLIT,$0
MOVL seh+0(FP), AX
MOVL AX, 0(FS)
RET
......@@ -177,17 +177,6 @@ func TestCallbackGC(t *testing.T) {
nestedCall(t, runtime.GC)
}
// NOTE: TestCallbackPanicLocked must precede the other TestCallbackPanic variants.
// The SEH logic is testing that SEH is properly restored during the panic.
// The bug we're looking for (issue 7470) used to leave SEH in the wrong place,
// but future panics would leave it in that same wrong place. So if one of the other
// tests runs first, TestCallbackPanicLocked will see SEH not changing and
// incorrectly infer that it is being restored properly.
// The SEH checks are only safe (not racy) with the OS thread locked.
//
// The fallback is that even if this test doesn't notice, TestSetPanicOnFault will
// crash if it runs on the same thread after one of these tests.
func TestCallbackPanicLocked(t *testing.T) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
......@@ -195,7 +184,6 @@ func TestCallbackPanicLocked(t *testing.T) {
if !runtime.LockedOSThread() {
t.Fatal("runtime.LockOSThread didn't")
}
oldSEH := runtime.GetSEH()
defer func() {
s := recover()
if s == nil {
......@@ -207,9 +195,6 @@ func TestCallbackPanicLocked(t *testing.T) {
if !runtime.LockedOSThread() {
t.Fatal("lost lock on OS thread after panic")
}
if newSEH := runtime.GetSEH(); oldSEH != newSEH {
t.Fatalf("SEH not restored after panic: %#x became %#x", oldSEH, newSEH)
}
}()
nestedCall(t, func() { panic("callback panic") })
panic("nestedCall returned")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment