[release-branch.go1.18] misc/cgo/testcarchive: permit SIGQUIT for TestSignalForwardingExternal

Occasionally the signal will be sent to a Go thread, which will cause
the program to exit with SIGQUIT rather than SIGSEGV.

Add TestSignalForwardingGo to test the case where the signal is
expected to be delivered to a Go thread.

This is a roll forward of CL 419014 which was rolled back in CL 424954.
This CL differs from 419014 in that it skips TestSignalForwardingGo
on darwin-amd64.

For #53907
Fixes #54056

Change-Id: I5df3fd610c068df3bd48d9b3d7a9379248b97999
Reviewed-on: https://go-review.googlesource.com/c/go/+/425002
Run-TryBot: Ian Lance Taylor <iant@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
(cherry picked from commit d05ce23756)
Reviewed-on: https://go-review.googlesource.com/c/go/+/425486
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Ian Lance Taylor 2022-08-19 14:43:47 -07:00 committed by Heschi Kreinick
parent bf812b32b0
commit db26851593
4 changed files with 169 additions and 98 deletions

View File

@ -19,6 +19,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
"testing" "testing"
"time" "time"
@ -518,38 +519,13 @@ func TestEarlySignalHandler(t *testing.T) {
func TestSignalForwarding(t *testing.T) { func TestSignalForwarding(t *testing.T) {
checkSignalForwardingTest(t) checkSignalForwardingTest(t)
buildSignalForwardingTest(t)
if !testWork { cmd := exec.Command(bin[0], append(bin[1:], "1")...)
defer func() {
os.Remove("libgo2.a")
os.Remove("libgo2.h")
os.Remove("testp" + exeSuffix)
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
checkLineComments(t, "libgo2.h")
checkArchive(t, "libgo2.a")
ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
if runtime.Compiler == "gccgo" {
ccArgs = append(ccArgs, "-lgo")
}
if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
cmd = exec.Command(bin[0], append(bin[1:], "1")...)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
t.Logf("%v\n%s", cmd.Args, out) t.Logf("%v\n%s", cmd.Args, out)
expectSignal(t, err, syscall.SIGSEGV) expectSignal(t, err, syscall.SIGSEGV, 0)
// SIGPIPE is never forwarded on darwin. See golang.org/issue/33384. // SIGPIPE is never forwarded on darwin. See golang.org/issue/33384.
if runtime.GOOS != "darwin" && runtime.GOOS != "ios" { if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
@ -560,7 +536,7 @@ func TestSignalForwarding(t *testing.T) {
if len(out) > 0 { if len(out) > 0 {
t.Logf("%s", out) t.Logf("%s", out)
} }
expectSignal(t, err, syscall.SIGPIPE) expectSignal(t, err, syscall.SIGPIPE, 0)
} }
} }
@ -571,32 +547,7 @@ func TestSignalForwardingExternal(t *testing.T) {
t.Skipf("skipping on %s/%s: runtime does not permit SI_USER SIGSEGV", GOOS, GOARCH) t.Skipf("skipping on %s/%s: runtime does not permit SI_USER SIGSEGV", GOOS, GOARCH)
} }
checkSignalForwardingTest(t) checkSignalForwardingTest(t)
buildSignalForwardingTest(t)
if !testWork {
defer func() {
os.Remove("libgo2.a")
os.Remove("libgo2.h")
os.Remove("testp" + exeSuffix)
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
}()
}
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
checkLineComments(t, "libgo2.h")
checkArchive(t, "libgo2.a")
ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
if runtime.Compiler == "gccgo" {
ccArgs = append(ccArgs, "-lgo")
}
if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
// We want to send the process a signal and see if it dies. // We want to send the process a signal and see if it dies.
// Normally the signal goes to the C thread, the Go signal // Normally the signal goes to the C thread, the Go signal
@ -609,42 +560,27 @@ func TestSignalForwardingExternal(t *testing.T) {
// fail. // fail.
const tries = 20 const tries = 20
for i := 0; i < tries; i++ { for i := 0; i < tries; i++ {
cmd = exec.Command(bin[0], append(bin[1:], "2")...) err := runSignalForwardingTest(t, "2")
stderr, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
defer stderr.Close()
r := bufio.NewReader(stderr)
err = cmd.Start()
if err != nil {
t.Fatal(err)
}
// Wait for trigger to ensure that the process is started.
ok, err := r.ReadString('\n')
// Verify trigger.
if err != nil || ok != "OK\n" {
t.Fatalf("Did not receive OK signal")
}
// Give the program a chance to enter the sleep function.
time.Sleep(time.Millisecond)
cmd.Process.Signal(syscall.SIGSEGV)
err = cmd.Wait()
if err == nil { if err == nil {
continue continue
} }
if expectSignal(t, err, syscall.SIGSEGV) { // If the signal is delivered to a C thread, as expected,
// the Go signal handler will disable itself and re-raise
// the signal, causing the program to die with SIGSEGV.
//
// It is also possible that the signal will be
// delivered to a Go thread, such as a GC thread.
// Currently when the Go runtime sees that a SIGSEGV was
// sent from a different program, it first tries to send
// the signal to the os/signal API. If nothing is looking
// for (or explicitly ignoring) SIGSEGV, then it crashes.
// Because the Go runtime is invoked via a c-archive,
// it treats this as GOTRACEBACK=crash, meaning that it
// dumps a stack trace for all goroutines, which it does
// by raising SIGQUIT. The effect is that we will see the
// program die with SIGQUIT in that case, not SIGSEGV.
if expectSignal(t, err, syscall.SIGSEGV, syscall.SIGQUIT) {
return return
} }
} }
@ -652,6 +588,23 @@ func TestSignalForwardingExternal(t *testing.T) {
t.Errorf("program succeeded unexpectedly %d times", tries) t.Errorf("program succeeded unexpectedly %d times", tries)
} }
func TestSignalForwardingGo(t *testing.T) {
// This test fails on darwin-amd64 because of the special
// handling of user-generated SIGSEGV signals in fixsigcode in
// runtime/signal_darwin_amd64.go.
if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
t.Skip("not supported on darwin-amd64")
}
checkSignalForwardingTest(t)
buildSignalForwardingTest(t)
err := runSignalForwardingTest(t, "4")
// Occasionally the signal will be delivered to a C thread,
// and the program will crash with SIGSEGV.
expectSignal(t, err, syscall.SIGQUIT, syscall.SIGSEGV)
}
// checkSignalForwardingTest calls t.Skip if the SignalForwarding test // checkSignalForwardingTest calls t.Skip if the SignalForwarding test
// doesn't work on this platform. // doesn't work on this platform.
func checkSignalForwardingTest(t *testing.T) { func checkSignalForwardingTest(t *testing.T) {
@ -666,18 +619,121 @@ func checkSignalForwardingTest(t *testing.T) {
} }
} }
// buildSignalForwardingTest builds the executable used by the various
// signal forwarding tests.
func buildSignalForwardingTest(t *testing.T) {
if !testWork {
t.Cleanup(func() {
os.Remove("libgo2.a")
os.Remove("libgo2.h")
os.Remove("testp" + exeSuffix)
os.RemoveAll(filepath.Join(GOPATH, "pkg"))
})
}
t.Log("go build -buildmode=c-archive -o libgo2.a ./libgo2")
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
out, err := cmd.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Fatal(err)
}
checkLineComments(t, "libgo2.h")
checkArchive(t, "libgo2.a")
ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
if runtime.Compiler == "gccgo" {
ccArgs = append(ccArgs, "-lgo")
}
t.Log(ccArgs)
out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Fatal(err)
}
}
func runSignalForwardingTest(t *testing.T, arg string) error {
t.Logf("%v %s", bin, arg)
cmd := exec.Command(bin[0], append(bin[1:], arg)...)
var out strings.Builder
cmd.Stdout = &out
stderr, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
defer stderr.Close()
r := bufio.NewReader(stderr)
err = cmd.Start()
if err != nil {
t.Fatal(err)
}
// Wait for trigger to ensure that process is started.
ok, err := r.ReadString('\n')
// Verify trigger.
if err != nil || ok != "OK\n" {
t.Fatal("Did not receive OK signal")
}
var wg sync.WaitGroup
wg.Add(1)
var errsb strings.Builder
go func() {
defer wg.Done()
io.Copy(&errsb, r)
}()
// Give the program a chance to enter the function.
// If the program doesn't get there the test will still
// pass, although it doesn't quite test what we intended.
// This is fine as long as the program normally makes it.
time.Sleep(time.Millisecond)
cmd.Process.Signal(syscall.SIGSEGV)
err = cmd.Wait()
s := out.String()
if len(s) > 0 {
t.Log(s)
}
wg.Wait()
s = errsb.String()
if len(s) > 0 {
t.Log(s)
}
return err
}
// expectSignal checks that err, the exit status of a test program, // expectSignal checks that err, the exit status of a test program,
// shows a failure due to a specific signal. Returns whether we found // shows a failure due to a specific signal or two. Returns whether we
// the expected signal. // found an expected signal.
func expectSignal(t *testing.T, err error, sig syscall.Signal) bool { func expectSignal(t *testing.T, err error, sig1, sig2 syscall.Signal) bool {
t.Helper()
if err == nil { if err == nil {
t.Error("test program succeeded unexpectedly") t.Error("test program succeeded unexpectedly")
} else if ee, ok := err.(*exec.ExitError); !ok { } else if ee, ok := err.(*exec.ExitError); !ok {
t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
} else if !ws.Signaled() || ws.Signal() != sig { } else if !ws.Signaled() || (ws.Signal() != sig1 && ws.Signal() != sig2) {
t.Errorf("got %v; expected signal %v", ee, sig) if sig2 == 0 {
t.Errorf("got %q; expected signal %q", ee, sig1)
} else {
t.Errorf("got %q; expected signal %q or %q", ee, sig1, sig2)
}
} else { } else {
return true return true
} }
@ -1013,14 +1069,14 @@ func TestCompileWithoutShared(t *testing.T) {
binArgs := append(cmdToRun(exe), "1") binArgs := append(cmdToRun(exe), "1")
out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput() out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
t.Logf("%v\n%s", binArgs, out) t.Logf("%v\n%s", binArgs, out)
expectSignal(t, err, syscall.SIGSEGV) expectSignal(t, err, syscall.SIGSEGV, 0)
// SIGPIPE is never forwarded on darwin. See golang.org/issue/33384. // SIGPIPE is never forwarded on darwin. See golang.org/issue/33384.
if runtime.GOOS != "darwin" && runtime.GOOS != "ios" { if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
binArgs := append(cmdToRun(exe), "3") binArgs := append(cmdToRun(exe), "3")
out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput() out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
t.Logf("%v\n%s", binArgs, out) t.Logf("%v\n%s", binArgs, out)
expectSignal(t, err, syscall.SIGPIPE) expectSignal(t, err, syscall.SIGPIPE, 0)
} }
} }

View File

@ -49,6 +49,12 @@ func RunGoroutines() {
} }
} }
// Block blocks the current thread while running Go code.
//export Block
func Block() {
select {}
}
var P *byte var P *byte
// TestSEGV makes sure that an invalid address turns into a run-time Go panic. // TestSEGV makes sure that an invalid address turns into a run-time Go panic.

View File

@ -29,10 +29,6 @@ int main(int argc, char** argv) {
verbose = (argc > 2); verbose = (argc > 2);
if (verbose) {
printf("calling RunGoroutines\n");
}
Noop(); Noop();
switch (test) { switch (test) {
@ -90,6 +86,15 @@ int main(int argc, char** argv) {
printf("did not receive SIGPIPE\n"); printf("did not receive SIGPIPE\n");
return 0; return 0;
} }
case 4: {
fprintf(stderr, "OK\n");
fflush(stderr);
if (verbose) {
printf("calling Block\n");
}
Block();
}
default: default:
printf("Unknown test: %d\n", test); printf("Unknown test: %d\n", test);
return 0; return 0;

View File

@ -84,6 +84,10 @@ func (c *sigctxt) fixsigcode(sig uint32) {
// in real life, people will probably search for it and find this code. // in real life, people will probably search for it and find this code.
// There are no Google hits for b01dfacedebac1e or 0xb01dfacedebac1e // There are no Google hits for b01dfacedebac1e or 0xb01dfacedebac1e
// as I type this comment. // as I type this comment.
//
// Note: if this code is removed, please consider
// enabling TestSignalForwardingGo for darwin-amd64 in
// misc/cgo/testcarchive/carchive_test.go.
if c.sigcode() == _SI_USER { if c.sigcode() == _SI_USER {
c.set_sigcode(_SI_USER + 1) c.set_sigcode(_SI_USER + 1)
c.set_sigaddr(0xb01dfacedebac1e) c.set_sigaddr(0xb01dfacedebac1e)