Commit 17a108ba authored by Alex Brainman's avatar Alex Brainman

runtime: handle all windows exception

Fixes #8006.

LGTM=rsc
R=golang-codereviews, rsc
CC=golang-codereviews
https://golang.org/cl/145150043
parent 9ca83684
......@@ -59,6 +59,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
......
......@@ -32,6 +32,9 @@ enum {
INFINITE = 0xffffffff,
WAIT_TIMEOUT = 0x102,
EXCEPTION_CONTINUE_EXECUTION = -0x1,
EXCEPTION_CONTINUE_SEARCH = 0x0,
};
typedef struct SystemInfo SystemInfo;
......
......@@ -32,6 +32,9 @@ enum {
INFINITE = 0xffffffff,
WAIT_TIMEOUT = 0x102,
EXCEPTION_CONTINUE_EXECUTION = -0x1,
EXCEPTION_CONTINUE_SEARCH = 0x0,
};
typedef struct SystemInfo SystemInfo;
......
......@@ -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,28 @@ void
runtime·osinit(void)
{
void *kernel32;
void *addVectoredContinueHandler = nil;
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);
if(kernel32 != nil)
addVectoredContinueHandler = runtime·stdcall2(runtime·GetProcAddress, (uintptr)kernel32, (uintptr)"AddVectoredContinueHandler");
if(addVectoredContinueHandler == nil)
// use SetUnhandledExceptionFilter 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 +140,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");
}
......@@ -475,10 +494,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
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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)
}
`
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