]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: handle all windows exception (second attempt)
authorAlex Brainman <alex.brainman@gmail.com>
Wed, 15 Oct 2014 00:11:11 +0000 (11:11 +1100)
committerAlex Brainman <alex.brainman@gmail.com>
Wed, 15 Oct 2014 00:11:11 +0000 (11:11 +1100)
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

src/runtime/defs_windows.go
src/runtime/defs_windows_386.h
src/runtime/defs_windows_amd64.h
src/runtime/os_windows.c
src/runtime/os_windows_386.c
src/runtime/os_windows_amd64.c
src/runtime/sys_windows_386.s
src/runtime/sys_windows_amd64.s
src/runtime/syscall_windows_test.go

index cb0f54d8abbd47e9ee68b593bd66937be9fb9d5f..7ce67974141c08ee53677a57b4c80675b1674516 100644 (file)
@@ -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
index 295e422c6bacac813953b200a18173b1ced6d3f7..2317c04f658bcf5a9aa4ad5d041d0dd778688659 100644 (file)
@@ -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;
index 2516c84128cba922aceace5468eb6a75f64147d2..7f37a7a8c3e5002fda54fef562b9da0322ff4ce2 100644 (file)
@@ -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;
index 6337dde2af8d8bd0a12e5522d3ceed031ecf0b13..b8b8eda5f33f6d00f73daefd01e2111a52125db6 100644 (file)
@@ -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
index e2ae8db277c55b0a72e57e49c64ee4f21cc808cf..213582799b13604fc36a08cadfce9a9d603bbd89 100644 (file)
@@ -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
index 261880d450115adfde677aceafda87b21be72472..b96cf70d1ec0726ee4930e87e2043c722d0a4bf7 100644 (file)
@@ -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
index 1bf4d062acdaffade513fdac55b4e2ff71cd14c0..932fe9dd2450c0eee0082d2b109d63080af3965e 100644 (file)
@@ -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)
index 05750398eaed1a655e02f416ad3390017bec4bc5..e6190ce684d70e7b4a7bea86e09bc8194178568f 100644 (file)
@@ -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
index 9ed016ccc8f7f9755e0f18ef86f15641f23f5c1e..ce8a9ec1ba9e974b9f270fa2f59c331b42df74ff 100644 (file)
@@ -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)
+}
+`