Commit c4efaac1 authored by Russ Cox's avatar Russ Cox

runtime: fix unrecovered panic on external thread

Fixes #8588.

LGTM=austin
R=austin
CC=golang-codereviews, khr
https://golang.org/cl/159700044
parent 5e568545
...@@ -732,6 +732,20 @@ needm: ...@@ -732,6 +732,20 @@ needm:
MOVL g(CX), BP MOVL g(CX), BP
MOVL g_m(BP), BP MOVL g_m(BP), BP
// Set m->sched.sp = SP, so that if a panic happens
// during the function we are about to execute, it will
// have a valid SP to run on the g0 stack.
// The next few lines (after the havem label)
// will save this SP onto the stack and then write
// the same SP back to m->sched.sp. That seems redundant,
// but if an unrecovered panic happens, unwindm will
// restore the g->sched.sp from the stack location
// and then onM will try to use it. If we don't set it here,
// that restored SP will be uninitialized (typically 0) and
// will not be usable.
MOVL m_g0(BP), SI
MOVL SP, (g_sched+gobuf_sp)(SI)
havem: havem:
// Now there's a valid m, and we're running on its m->g0. // Now there's a valid m, and we're running on its m->g0.
// Save current m->g0->sched.sp on stack and then set it to SP. // Save current m->g0->sched.sp on stack and then set it to SP.
......
...@@ -717,6 +717,20 @@ needm: ...@@ -717,6 +717,20 @@ needm:
get_tls(CX) get_tls(CX)
MOVQ g(CX), BP MOVQ g(CX), BP
MOVQ g_m(BP), BP MOVQ g_m(BP), BP
// Set m->sched.sp = SP, so that if a panic happens
// during the function we are about to execute, it will
// have a valid SP to run on the g0 stack.
// The next few lines (after the havem label)
// will save this SP onto the stack and then write
// the same SP back to m->sched.sp. That seems redundant,
// but if an unrecovered panic happens, unwindm will
// restore the g->sched.sp from the stack location
// and then onM will try to use it. If we don't set it here,
// that restored SP will be uninitialized (typically 0) and
// will not be usable.
MOVQ m_g0(BP), SI
MOVQ SP, (g_sched+gobuf_sp)(SI)
havem: havem:
// Now there's a valid m, and we're running on its m->g0. // Now there's a valid m, and we're running on its m->g0.
......
...@@ -556,6 +556,21 @@ TEXT ·cgocallback_gofunc(SB),NOSPLIT,$8-12 ...@@ -556,6 +556,21 @@ TEXT ·cgocallback_gofunc(SB),NOSPLIT,$8-12
MOVW $runtime·needm(SB), R0 MOVW $runtime·needm(SB), R0
BL (R0) BL (R0)
// Set m->sched.sp = SP, so that if a panic happens
// during the function we are about to execute, it will
// have a valid SP to run on the g0 stack.
// The next few lines (after the havem label)
// will save this SP onto the stack and then write
// the same SP back to m->sched.sp. That seems redundant,
// but if an unrecovered panic happens, unwindm will
// restore the g->sched.sp from the stack location
// and then onM will try to use it. If we don't set it here,
// that restored SP will be uninitialized (typically 0) and
// will not be usable.
MOVW g_m(g), R8
MOVW m_g0(R8), R3
MOVW R13, (g_sched+gobuf_sp)(R3)
havem: havem:
MOVW g_m(g), R8 MOVW g_m(g), R8
MOVW R8, savedm-4(SP) MOVW R8, savedm-4(SP)
......
...@@ -8,6 +8,7 @@ package runtime_test ...@@ -8,6 +8,7 @@ package runtime_test
import ( import (
"runtime" "runtime"
"strings"
"testing" "testing"
) )
...@@ -34,6 +35,14 @@ func TestCgoTraceback(t *testing.T) { ...@@ -34,6 +35,14 @@ func TestCgoTraceback(t *testing.T) {
} }
} }
func TestCgoExternalThreadPanic(t *testing.T) {
got := executeTest(t, cgoExternalThreadPanicSource, nil, "main.c", cgoExternalThreadPanicC)
want := "panic: BOOM"
if !strings.Contains(got, want) {
t.Fatalf("want failure containing %q. output:\n%s\n", want, got)
}
}
const cgoSignalDeadlockSource = ` const cgoSignalDeadlockSource = `
package main package main
...@@ -117,3 +126,43 @@ func main() { ...@@ -117,3 +126,43 @@ func main() {
fmt.Printf("OK\n") fmt.Printf("OK\n")
} }
` `
const cgoExternalThreadPanicSource = `
package main
// void start(void);
import "C"
func main() {
C.start()
select {}
}
//export gopanic
func gopanic() {
panic("BOOM")
}
`
const cgoExternalThreadPanicC = `
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
void gopanic(void);
static void*
die(void* x)
{
gopanic();
return 0;
}
void
start(void)
{
pthread_t t;
if(pthread_create(&t, 0, die, 0) != 0)
printf("pthread_create failed\n");
}
`
...@@ -31,7 +31,7 @@ func testEnv(cmd *exec.Cmd) *exec.Cmd { ...@@ -31,7 +31,7 @@ func testEnv(cmd *exec.Cmd) *exec.Cmd {
return cmd return cmd
} }
func executeTest(t *testing.T, templ string, data interface{}) string { func executeTest(t *testing.T, templ string, data interface{}, extra ...string) string {
switch runtime.GOOS { switch runtime.GOOS {
case "android", "nacl": case "android", "nacl":
t.Skipf("skipping on %s", runtime.GOOS) t.Skipf("skipping on %s", runtime.GOOS)
...@@ -61,7 +61,20 @@ func executeTest(t *testing.T, templ string, data interface{}) string { ...@@ -61,7 +61,20 @@ func executeTest(t *testing.T, templ string, data interface{}) string {
t.Fatalf("failed to close file: %v", err) t.Fatalf("failed to close file: %v", err)
} }
got, _ := testEnv(exec.Command("go", "run", src)).CombinedOutput() for i := 0; i < len(extra); i += 2 {
if err := ioutil.WriteFile(filepath.Join(dir, extra[i]), []byte(extra[i+1]), 0666); err != nil {
t.Fatal(err)
}
}
cmd := exec.Command("go", "build", "-o", "a.exe")
cmd.Dir = dir
out, err := testEnv(cmd).CombinedOutput()
if err != nil {
t.Fatalf("building source: %v\n%s", err, out)
}
got, _ := testEnv(exec.Command(filepath.Join(dir, "a.exe"))).CombinedOutput()
return string(got) return string(got)
} }
......
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