From: Alex Brainman Date: Wed, 15 Oct 2014 00:11:11 +0000 (+1100) Subject: runtime: handle all windows exception (second attempt) X-Git-Tag: go1.4beta1~126 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=e9ecd4aec51f06cb0834fe3bbd8c4b88d5fd94b5;p=gostls13.git runtime: handle all windows exception (second attempt) includes undo of 22318cd31d7d and also: - always use SetUnhandledExceptionFilter on windows-386; - crash when receive EXCEPTION_BREAKPOINT in exception handler. Fixes #8006. LGTM=rsc R=golang-codereviews, rsc CC=golang-codereviews https://golang.org/cl/155360043 --- diff --git a/src/runtime/defs_windows.go b/src/runtime/defs_windows.go index cb0f54d8ab..7ce6797414 100644 --- a/src/runtime/defs_windows.go +++ b/src/runtime/defs_windows.go @@ -49,6 +49,7 @@ const ( CONTEXT_FULL = C.CONTEXT_FULL EXCEPTION_ACCESS_VIOLATION = C.STATUS_ACCESS_VIOLATION + EXCEPTION_BREAKPOINT = C.STATUS_BREAKPOINT EXCEPTION_FLT_DENORMAL_OPERAND = C.STATUS_FLOAT_DENORMAL_OPERAND EXCEPTION_FLT_DIVIDE_BY_ZERO = C.STATUS_FLOAT_DIVIDE_BY_ZERO EXCEPTION_FLT_INEXACT_RESULT = C.STATUS_FLOAT_INEXACT_RESULT @@ -59,6 +60,9 @@ const ( INFINITE = C.INFINITE WAIT_TIMEOUT = C.WAIT_TIMEOUT + + EXCEPTION_CONTINUE_EXECUTION = C.EXCEPTION_CONTINUE_EXECUTION + EXCEPTION_CONTINUE_SEARCH = C.EXCEPTION_CONTINUE_SEARCH ) type SystemInfo C.SYSTEM_INFO diff --git a/src/runtime/defs_windows_386.h b/src/runtime/defs_windows_386.h index 295e422c6b..2317c04f65 100644 --- a/src/runtime/defs_windows_386.h +++ b/src/runtime/defs_windows_386.h @@ -22,6 +22,7 @@ enum { CONTEXT_FULL = 0x10007, EXCEPTION_ACCESS_VIOLATION = 0xc0000005, + EXCEPTION_BREAKPOINT = 0x80000003, EXCEPTION_FLT_DENORMAL_OPERAND = 0xc000008d, EXCEPTION_FLT_DIVIDE_BY_ZERO = 0xc000008e, EXCEPTION_FLT_INEXACT_RESULT = 0xc000008f, @@ -32,6 +33,9 @@ enum { INFINITE = 0xffffffff, WAIT_TIMEOUT = 0x102, + + EXCEPTION_CONTINUE_EXECUTION = -0x1, + EXCEPTION_CONTINUE_SEARCH = 0x0, }; typedef struct SystemInfo SystemInfo; diff --git a/src/runtime/defs_windows_amd64.h b/src/runtime/defs_windows_amd64.h index 2516c84128..7f37a7a8c3 100644 --- a/src/runtime/defs_windows_amd64.h +++ b/src/runtime/defs_windows_amd64.h @@ -22,6 +22,7 @@ enum { CONTEXT_FULL = 0x10000b, EXCEPTION_ACCESS_VIOLATION = 0xc0000005, + EXCEPTION_BREAKPOINT = 0x80000003, EXCEPTION_FLT_DENORMAL_OPERAND = 0xc000008d, EXCEPTION_FLT_DIVIDE_BY_ZERO = 0xc000008e, EXCEPTION_FLT_INEXACT_RESULT = 0xc000008f, @@ -32,6 +33,9 @@ enum { INFINITE = 0xffffffff, WAIT_TIMEOUT = 0x102, + + EXCEPTION_CONTINUE_EXECUTION = -0x1, + EXCEPTION_CONTINUE_SEARCH = 0x0, }; typedef struct SystemInfo SystemInfo; diff --git a/src/runtime/os_windows.c b/src/runtime/os_windows.c index 6337dde2af..b8b8eda5f3 100644 --- a/src/runtime/os_windows.c +++ b/src/runtime/os_windows.c @@ -34,6 +34,7 @@ #pragma dynimport runtime·SetEvent SetEvent "kernel32.dll" #pragma dynimport runtime·SetProcessPriorityBoost SetProcessPriorityBoost "kernel32.dll" #pragma dynimport runtime·SetThreadPriority SetThreadPriority "kernel32.dll" +#pragma dynimport runtime·SetUnhandledExceptionFilter SetUnhandledExceptionFilter "kernel32.dll" #pragma dynimport runtime·SetWaitableTimer SetWaitableTimer "kernel32.dll" #pragma dynimport runtime·Sleep Sleep "kernel32.dll" #pragma dynimport runtime·SuspendThread SuspendThread "kernel32.dll" @@ -65,6 +66,7 @@ extern void *runtime·SetConsoleCtrlHandler; extern void *runtime·SetEvent; extern void *runtime·SetProcessPriorityBoost; extern void *runtime·SetThreadPriority; +extern void *runtime·SetUnhandledExceptionFilter; extern void *runtime·SetWaitableTimer; extern void *runtime·Sleep; extern void *runtime·SuspendThread; @@ -77,7 +79,9 @@ void *runtime·GetQueuedCompletionStatusEx; extern uintptr runtime·externalthreadhandlerp; void runtime·externalthreadhandler(void); -void runtime·sigtramp(void); +void runtime·exceptiontramp(void); +void runtime·firstcontinuetramp(void); +void runtime·lastcontinuetramp(void); #pragma textflag NOSPLIT uintptr @@ -106,12 +110,30 @@ void runtime·osinit(void) { void *kernel32; + void *addVectoredContinueHandler; + + kernel32 = runtime·stdcall1(runtime·LoadLibraryA, (uintptr)"kernel32.dll"); runtime·externalthreadhandlerp = (uintptr)runtime·externalthreadhandler; - runtime·stdcall2(runtime·AddVectoredExceptionHandler, 1, (uintptr)runtime·sigtramp); + runtime·stdcall2(runtime·AddVectoredExceptionHandler, 1, (uintptr)runtime·exceptiontramp); + addVectoredContinueHandler = nil; + if(kernel32 != nil) + addVectoredContinueHandler = runtime·stdcall2(runtime·GetProcAddress, (uintptr)kernel32, (uintptr)"AddVectoredContinueHandler"); + if(addVectoredContinueHandler == nil || sizeof(void*) == 4) { + // use SetUnhandledExceptionFilter for windows-386 or + // if VectoredContinueHandler is unavailable. + // note: SetUnhandledExceptionFilter handler won't be called, if debugging. + runtime·stdcall1(runtime·SetUnhandledExceptionFilter, (uintptr)runtime·lastcontinuetramp); + } else { + runtime·stdcall2(addVectoredContinueHandler, 1, (uintptr)runtime·firstcontinuetramp); + runtime·stdcall2(addVectoredContinueHandler, 0, (uintptr)runtime·lastcontinuetramp); + } + runtime·stdcall2(runtime·SetConsoleCtrlHandler, (uintptr)runtime·ctrlhandler, 1); + runtime·stdcall1(runtime·timeBeginPeriod, 1); + runtime·ncpu = getproccount(); // Windows dynamic priority boosting assumes that a process has different types @@ -120,7 +142,6 @@ runtime·osinit(void) // In such context dynamic priority boosting does nothing but harm, so we turn it off. runtime·stdcall2(runtime·SetProcessPriorityBoost, -1, 1); - kernel32 = runtime·stdcall1(runtime·LoadLibraryA, (uintptr)"kernel32.dll"); if(kernel32 != nil) { runtime·GetQueuedCompletionStatusEx = runtime·stdcall2(runtime·GetProcAddress, (uintptr)kernel32, (uintptr)"GetQueuedCompletionStatusEx"); } @@ -467,6 +488,7 @@ runtime·issigpanic(uint32 code) case EXCEPTION_FLT_INEXACT_RESULT: case EXCEPTION_FLT_OVERFLOW: case EXCEPTION_FLT_UNDERFLOW: + case EXCEPTION_BREAKPOINT: return 1; } return 0; @@ -475,10 +497,14 @@ runtime·issigpanic(uint32 code) void runtime·initsig(void) { - // following line keeps sigtramp alive at link stage + // following line keeps these functions alive at link stage // if there's a better way please write it here - void *p = runtime·sigtramp; - USED(p); + void *e = runtime·exceptiontramp; + void *f = runtime·firstcontinuetramp; + void *l = runtime·lastcontinuetramp; + USED(e); + USED(f); + USED(l); } uint32 diff --git a/src/runtime/os_windows_386.c b/src/runtime/os_windows_386.c index e2ae8db277..213582799b 100644 --- a/src/runtime/os_windows_386.c +++ b/src/runtime/os_windows_386.c @@ -24,45 +24,63 @@ runtime·dumpregs(Context *r) runtime·printf("gs %x\n", r->SegGs); } -// 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 +runtime·isgoexception(ExceptionRecord *info, Context *r) { - bool crash; - uintptr *sp; extern byte runtime·text[], runtime·etext[]; // Only handle exception if executing instructions in Go binary // (not Windows library code). if(r->Eip < (uint32)runtime·text || (uint32)runtime·etext < r->Eip) - return 0; - - if(gp != 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]; - gp->sigpc = r->Eip; - - // 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 -1; + return false; + + if(!runtime·issigpanic(info->ExceptionCode)) + return false; + + return true; +} + +// Called by sigtramp from Windows VEH handler. +// Return value signals whether the exception has been handled (EXCEPTION_CONTINUE_EXECUTION) +// or should be made available to other handlers in the chain (EXCEPTION_CONTINUE_SEARCH). +uint32 +runtime·exceptionhandler(ExceptionRecord *info, Context *r, G *gp) +{ + uintptr *sp; + + if(!runtime·isgoexception(info, r)) + return EXCEPTION_CONTINUE_SEARCH; + + // 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]; + gp->sigpc = r->Eip; + + // 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 EXCEPTION_CONTINUE_EXECUTION; +} + +// lastcontinuehandler is reached, because runtime cannot handle +// current exception. lastcontinuehandler will print crash info and exit. +uint32 +runtime·lastcontinuehandler(ExceptionRecord *info, Context *r, G *gp) +{ + bool crash; if(runtime·panicking) // traceback already printed runtime·exit(2); @@ -88,7 +106,7 @@ runtime·sighandler(ExceptionRecord *info, Context *r, G *gp) runtime·crash(); runtime·exit(2); - return -1; // not reached + return 0; // not reached } void diff --git a/src/runtime/os_windows_amd64.c b/src/runtime/os_windows_amd64.c index 261880d450..b96cf70d1e 100644 --- a/src/runtime/os_windows_amd64.c +++ b/src/runtime/os_windows_amd64.c @@ -32,45 +32,76 @@ runtime·dumpregs(Context *r) runtime·printf("gs %X\n", (uint64)r->SegGs); } -// 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 +runtime·isgoexception(ExceptionRecord *info, Context *r) { - bool crash; - uintptr *sp; extern byte runtime·text[], runtime·etext[]; // Only handle exception if executing instructions in Go binary // (not Windows library code). if(r->Rip < (uint64)runtime·text || (uint64)runtime·etext < r->Rip) - return 0; - - if(gp != 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]; - gp->sigpc = r->Rip; - - // Only push runtime·sigpanic if r->rip != 0. - // If r->rip == 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->Rip != 0) { - sp = (uintptr*)r->Rsp; - *--sp = r->Rip; - r->Rsp = (uintptr)sp; - } - r->Rip = (uintptr)runtime·sigpanic; - return -1; + return false; + + if(!runtime·issigpanic(info->ExceptionCode)) + return false; + + return true; +} + +// Called by sigtramp from Windows VEH handler. +// Return value signals whether the exception has been handled (EXCEPTION_CONTINUE_EXECUTION) +// or should be made available to other handlers in the chain (EXCEPTION_CONTINUE_SEARCH). +uint32 +runtime·exceptionhandler(ExceptionRecord *info, Context *r, G *gp) +{ + uintptr *sp; + + if(!runtime·isgoexception(info, r)) + return EXCEPTION_CONTINUE_SEARCH; + + // 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]; + gp->sigpc = r->Rip; + + // Only push runtime·sigpanic if r->rip != 0. + // If r->rip == 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->Rip != 0) { + sp = (uintptr*)r->Rsp; + *--sp = r->Rip; + r->Rsp = (uintptr)sp; } + r->Rip = (uintptr)runtime·sigpanic; + return EXCEPTION_CONTINUE_EXECUTION; +} + +// It seems Windows searches ContinueHandler's list even +// if ExceptionHandler returns EXCEPTION_CONTINUE_EXECUTION. +// firstcontinuehandler will stop that search, +// if exceptionhandler did the same earlier. +uint32 +runtime·firstcontinuehandler(ExceptionRecord *info, Context *r, G *gp) +{ + USED(gp); + if(!runtime·isgoexception(info, r)) + return EXCEPTION_CONTINUE_SEARCH; + return EXCEPTION_CONTINUE_EXECUTION; +} + +// lastcontinuehandler is reached, because runtime cannot handle +// current exception. lastcontinuehandler will print crash info and exit. +uint32 +runtime·lastcontinuehandler(ExceptionRecord *info, Context *r, G *gp) +{ + bool crash; if(runtime·panicking) // traceback already printed runtime·exit(2); @@ -97,7 +128,7 @@ runtime·sighandler(ExceptionRecord *info, Context *r, G *gp) runtime·crash(); runtime·exit(2); - return -1; // not reached + return 0; // not reached } void diff --git a/src/runtime/sys_windows_386.s b/src/runtime/sys_windows_386.s index 1bf4d062ac..932fe9dd24 100644 --- a/src/runtime/sys_windows_386.s +++ b/src/runtime/sys_windows_386.s @@ -73,6 +73,7 @@ TEXT runtime·setlasterror(SB),NOSPLIT,$0 // Called by Windows as a Vectored Exception Handler (VEH). // First argument is pointer to struct containing // exception record and context pointers. +// Handler function is stored in AX. // Return 0 for 'not handled', -1 for handled. TEXT runtime·sigtramp(SB),NOSPLIT,$0-0 MOVL ptrs+0(FP), CX @@ -84,6 +85,8 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0 MOVL SI, 20(SP) MOVL DI, 24(SP) + MOVL AX, SI // save handler address + // find g get_tls(DX) CMPL DX, $0 @@ -123,11 +126,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0 sigtramp_g0: MOVL 0(CX), BX // ExceptionRecord* MOVL 4(CX), CX // Context* - // call sighandler(ExceptionRecord*, Context*, G*) MOVL BX, 0(SP) MOVL CX, 4(SP) MOVL DX, 8(SP) - CALL runtime·sighandler(SB) + CALL SI // call handler // AX is set to report result back to Windows MOVL 12(SP), AX @@ -149,6 +151,18 @@ done: // RET 4 (return and pop 4 bytes parameters) BYTE $0xC2; WORD $4 RET // unreached; make assembler happy + +TEXT runtime·exceptiontramp(SB),NOSPLIT,$0 + MOVL $runtime·exceptionhandler(SB), AX + JMP runtime·sigtramp(SB) + +TEXT runtime·firstcontinuetramp(SB),NOSPLIT,$0-0 + // is never called + INT $3 + +TEXT runtime·lastcontinuetramp(SB),NOSPLIT,$0-0 + MOVL $runtime·lastcontinuehandler(SB), AX + JMP runtime·sigtramp(SB) TEXT runtime·ctrlhandler(SB),NOSPLIT,$0 PUSHL $runtime·ctrlhandler1(SB) diff --git a/src/runtime/sys_windows_amd64.s b/src/runtime/sys_windows_amd64.s index 05750398ea..e6190ce684 100644 --- a/src/runtime/sys_windows_amd64.s +++ b/src/runtime/sys_windows_amd64.s @@ -99,6 +99,7 @@ TEXT runtime·setlasterror(SB),NOSPLIT,$0 // Called by Windows as a Vectored Exception Handler (VEH). // First argument is pointer to struct containing // exception record and context pointers. +// Handler function is stored in AX. // Return 0 for 'not handled', -1 for handled. TEXT runtime·sigtramp(SB),NOSPLIT,$0-0 // CX: PEXCEPTION_POINTERS ExceptionInfo @@ -116,6 +117,8 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0 MOVQ R14, 32(SP) MOVQ R15, 88(SP) + MOVQ AX, R15 // save handler address + // find g get_tls(DX) CMPQ DX, $0 @@ -157,11 +160,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0 sigtramp_g0: MOVQ 0(CX), BX // ExceptionRecord* MOVQ 8(CX), CX // Context* - // call sighandler(ExceptionRecord*, Context*, G*) MOVQ BX, 0(SP) MOVQ CX, 8(SP) MOVQ DX, 16(SP) - CALL runtime·sighandler(SB) + CALL R15 // call handler // AX is set to report result back to Windows MOVL 24(SP), AX @@ -187,6 +189,18 @@ done: RET +TEXT runtime·exceptiontramp(SB),NOSPLIT,$0 + MOVQ $runtime·exceptionhandler(SB), AX + JMP runtime·sigtramp(SB) + +TEXT runtime·firstcontinuetramp(SB),NOSPLIT,$0-0 + MOVQ $runtime·firstcontinuehandler(SB), AX + JMP runtime·sigtramp(SB) + +TEXT runtime·lastcontinuetramp(SB),NOSPLIT,$0-0 + MOVQ $runtime·lastcontinuehandler(SB), AX + JMP runtime·sigtramp(SB) + TEXT runtime·ctrlhandler(SB),NOSPLIT,$8 MOVQ CX, 16(SP) // spill MOVQ $runtime·ctrlhandler1(SB), CX diff --git a/src/runtime/syscall_windows_test.go b/src/runtime/syscall_windows_test.go index 9ed016ccc8..ce8a9ec1ba 100644 --- a/src/runtime/syscall_windows_test.go +++ b/src/runtime/syscall_windows_test.go @@ -494,3 +494,42 @@ func TestOutputDebugString(t *testing.T) { p := syscall.StringToUTF16Ptr("testing OutputDebugString") d.Proc("OutputDebugStringW").Call(uintptr(unsafe.Pointer(p))) } + +func TestRaiseException(t *testing.T) { + o := executeTest(t, raiseExceptionSource, nil) + if strings.Contains(o, "RaiseException should not return") { + t.Fatalf("RaiseException did not crash program: %v", o) + } + if !strings.Contains(o, "Exception 0xbad") { + t.Fatalf("No stack trace: %v", o) + } +} + +const raiseExceptionSource = ` +package main +import "syscall" +func main() { + const EXCEPTION_NONCONTINUABLE = 1 + mod := syscall.MustLoadDLL("kernel32.dll") + proc := mod.MustFindProc("RaiseException") + proc.Call(0xbad, EXCEPTION_NONCONTINUABLE, 0, 0) + println("RaiseException should not return") +} +` + +func TestZeroDivisionException(t *testing.T) { + o := executeTest(t, zeroDivisionExceptionSource, nil) + if !strings.Contains(o, "panic: runtime error: integer divide by zero") { + t.Fatalf("No stack trace: %v", o) + } +} + +const zeroDivisionExceptionSource = ` +package main +func main() { + x := 1 + y := 0 + z := x / y + println(z) +} +`