mirror of https://github.com/golang/go.git
[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:
parent
bf812b32b0
commit
db26851593
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue