Skip to content

Commit e5e5fb0

Browse files
simonferquelalexbrainman
authored andcommitted
runtime: do not crash in lastcontinuehandler when running as DLL
If Go DLL is used by external C program, and lastcontinuehandler is reached, lastcontinuehandler will crash the process it is running in. But it should not be up to Go runtime to decide if process to be crashed or not - it should be up to C runtime. This CL adjusts lastcontinuehandler to not to crash when running as DLL. Fixes #32648. Change-Id: Ia455e69b8dde2a6f42f06b90e8af4aa322ca269a GitHub-Last-Rev: dbdffcb GitHub-Pull-Request: #32574 Reviewed-on: https://go-review.googlesource.com/c/go/+/181839 Run-TryBot: Alex Brainman <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Alex Brainman <[email protected]>
1 parent 1786ecd commit e5e5fb0

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

src/runtime/signal_windows.go

+6
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ var testingWER bool
171171
//
172172
//go:nosplit
173173
func lastcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
174+
if islibrary || isarchive {
175+
// Go DLL/archive has been loaded in a non-go program.
176+
// If the exception does not originate from go, the go runtime
177+
// should not take responsibility of crashing the process.
178+
return _EXCEPTION_CONTINUE_SEARCH
179+
}
174180
if testingWER {
175181
return _EXCEPTION_CONTINUE_SEARCH
176182
}

src/runtime/signal_windows_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// +build windows
2+
3+
package runtime_test
4+
5+
import (
6+
"internal/testenv"
7+
"io/ioutil"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"runtime"
12+
"strings"
13+
"testing"
14+
)
15+
16+
func TestVectoredHandlerDontCrashOnLibrary(t *testing.T) {
17+
if *flagQuick {
18+
t.Skip("-quick")
19+
}
20+
if runtime.GOARCH != "amd64" {
21+
t.Skip("this test can only run on windows/amd64")
22+
}
23+
testenv.MustHaveGoBuild(t)
24+
testenv.MustHaveExecPath(t, "gcc")
25+
testprog.Lock()
26+
defer testprog.Unlock()
27+
dir, err := ioutil.TempDir("", "go-build")
28+
if err != nil {
29+
t.Fatalf("failed to create temp directory: %v", err)
30+
}
31+
defer os.Remove(dir)
32+
33+
// build go dll
34+
dll := filepath.Join(dir, "testwinlib.dll")
35+
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dll, "--buildmode", "c-shared", "testdata/testwinlib/main.go")
36+
out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
37+
if err != nil {
38+
t.Fatalf("failed to build go library: %s\n%s", err, out)
39+
}
40+
41+
// build c program
42+
exe := filepath.Join(dir, "test.exe")
43+
cmd = exec.Command("gcc", "-L"+dir, "-I"+dir, "-ltestwinlib", "-o", exe, "testdata/testwinlib/main.c")
44+
out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
45+
if err != nil {
46+
t.Fatalf("failed to build c exe: %s\n%s", err, out)
47+
}
48+
49+
// run test program
50+
cmd = exec.Command(exe)
51+
out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
52+
if err != nil {
53+
t.Fatalf("failure while running executable: %s\n%s", err, out)
54+
}
55+
expectedOutput := "exceptionCount: 1\ncontinueCount: 1\n"
56+
// cleaning output
57+
cleanedOut := strings.ReplaceAll(string(out), "\r\n", "\n")
58+
if cleanedOut != expectedOutput {
59+
t.Errorf("expected output %q, got %q", expectedOutput, cleanedOut)
60+
}
61+
}
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <stdio.h>
2+
#include <windows.h>
3+
#include "testwinlib.h"
4+
5+
int exceptionCount;
6+
int continueCount;
7+
LONG WINAPI customExceptionHandlder(struct _EXCEPTION_POINTERS *ExceptionInfo)
8+
{
9+
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
10+
{
11+
exceptionCount++;
12+
// prepare context to resume execution
13+
CONTEXT *c = ExceptionInfo->ContextRecord;
14+
c->Rip = *(ULONG_PTR *)c->Rsp;
15+
c->Rsp += 8;
16+
return EXCEPTION_CONTINUE_EXECUTION;
17+
}
18+
return EXCEPTION_CONTINUE_SEARCH;
19+
}
20+
LONG WINAPI customContinueHandlder(struct _EXCEPTION_POINTERS *ExceptionInfo)
21+
{
22+
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
23+
{
24+
continueCount++;
25+
return EXCEPTION_CONTINUE_EXECUTION;
26+
}
27+
return EXCEPTION_CONTINUE_SEARCH;
28+
}
29+
30+
void throwFromC()
31+
{
32+
DebugBreak();
33+
}
34+
int main()
35+
{
36+
// simulate a "lazily" attached debugger, by calling some go code before attaching the exception/continue handler
37+
Dummy();
38+
exceptionCount = 0;
39+
continueCount = 0;
40+
void *exceptionHandlerHandle = AddVectoredExceptionHandler(0, customExceptionHandlder);
41+
if (NULL == exceptionHandlerHandle)
42+
{
43+
printf("cannot add vectored exception handler\n");
44+
return 2;
45+
}
46+
void *continueHandlerHandle = AddVectoredContinueHandler(0, customContinueHandlder);
47+
if (NULL == continueHandlerHandle)
48+
{
49+
printf("cannot add vectored continue handler\n");
50+
return 2;
51+
}
52+
CallMeBack(throwFromC);
53+
RemoveVectoredContinueHandler(continueHandlerHandle);
54+
RemoveVectoredExceptionHandler(exceptionHandlerHandle);
55+
printf("exceptionCount: %d\ncontinueCount: %d\n", exceptionCount, continueCount);
56+
return 0;
57+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// +build windows,cgo
2+
3+
package main
4+
5+
// #include <windows.h>
6+
// typedef void(*callmeBackFunc)();
7+
// static void bridgeCallback(callmeBackFunc callback) {
8+
// callback();
9+
//}
10+
import "C"
11+
12+
// CallMeBack call backs C code.
13+
//export CallMeBack
14+
func CallMeBack(callback C.callmeBackFunc) {
15+
C.bridgeCallback(callback)
16+
}
17+
18+
// Dummy is called by the C code before registering the exception/continue handlers simulating a debugger.
19+
// This makes sure that the Go runtime's lastcontinuehandler is reached before the C continue handler and thus,
20+
// validate that it does not crash the program before another handler could take an action.
21+
// The idea here is to reproduce what happens when you attach a debugger to a running program.
22+
// It also simulate the behavior of the .Net debugger, which register its exception/continue handlers lazily.
23+
//export Dummy
24+
func Dummy() int {
25+
return 42
26+
}
27+
28+
func main() {}

0 commit comments

Comments
 (0)