| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build (freebsd && (386 || amd64 || arm || arm64 || riscv64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || mips64 || mips64le || ppc64 || ppc64le || riscv64 || s390x)) | |
| package runtime_test | |
| import ( | |
| "bytes" | |
| "internal/testenv" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "syscall" | |
| "testing" | |
| "time" | |
| ) | |
| // TestUsingVDSO tests that we are actually using the VDSO to fetch | |
| // the time. | |
| func TestUsingVDSO(t *testing.T) { | |
| const calls = 100 | |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { | |
| // Fetch the time a lot. | |
| var total int64 | |
| for i := 0; i < calls; i++ { | |
| total += time.Now().UnixNano() | |
| } | |
| os.Exit(0) | |
| } | |
| t.Parallel() | |
| // Look for strace in /bin or /usr/bin. Don't assume that some | |
| // strace on PATH is the one that we want. | |
| strace := "/bin/strace" | |
| if _, err := os.Stat(strace); err != nil { | |
| strace = "/usr/bin/strace" | |
| if _, err := os.Stat(strace); err != nil { | |
| t.Skipf("skipping test because strace not found: %v", err) | |
| } | |
| } | |
| exe, err := os.Executable() | |
| if err != nil { | |
| t.Skipf("skipping because Executable failed: %v", err) | |
| } | |
| t.Logf("GO_WANT_HELPER_PROCESS=1 %s -f -e clock_gettime %s -test.run=^TestUsingVDSO$", strace, exe) | |
| cmd := testenv.Command(t, strace, "-f", "-e", "clock_gettime", exe, "-test.run=^TestUsingVDSO$") | |
| cmd = testenv.CleanCmdEnv(cmd) | |
| cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1") | |
| out, err := cmd.CombinedOutput() | |
| if len(out) > 0 { | |
| t.Logf("%s", out) | |
| } | |
| if err != nil { | |
| if err := err.(*exec.ExitError); err != nil && err.Sys().(syscall.WaitStatus).Signaled() { | |
| if !bytes.Contains(out, []byte("+++ killed by")) { | |
| // strace itself occasionally crashes. | |
| // Here, it exited with a signal, but | |
| // the strace log didn't report any | |
| // signal from the child process. | |
| t.Log(err) | |
| testenv.SkipFlaky(t, 63734) | |
| } | |
| } | |
| t.Fatal(err) | |
| } | |
| if got := bytes.Count(out, []byte("gettime")); got >= calls { | |
| t.Logf("found %d gettime calls, want < %d", got, calls) | |
| // Try to double-check that a C program uses the VDSO. | |
| tempdir := t.TempDir() | |
| cfn := filepath.Join(tempdir, "time.c") | |
| cexe := filepath.Join(tempdir, "time") | |
| if err := os.WriteFile(cfn, []byte(vdsoCProgram), 0o644); err != nil { | |
| t.Fatal(err) | |
| } | |
| cc := os.Getenv("CC") | |
| if cc == "" { | |
| cc, err = exec.LookPath("gcc") | |
| if err != nil { | |
| cc, err = exec.LookPath("clang") | |
| if err != nil { | |
| t.Skip("can't verify VDSO status, no C compiler") | |
| } | |
| } | |
| } | |
| t.Logf("%s -o %s %s", cc, cexe, cfn) | |
| cmd = testenv.Command(t, cc, "-o", cexe, cfn) | |
| cmd = testenv.CleanCmdEnv(cmd) | |
| out, err = cmd.CombinedOutput() | |
| if len(out) > 0 { | |
| t.Logf("%s", out) | |
| } | |
| if err != nil { | |
| t.Skipf("can't verify VDSO status, C compiled failed: %v", err) | |
| } | |
| t.Logf("%s -f -e clock_gettime %s", strace, cexe) | |
| cmd = testenv.Command(t, strace, "-f", "-e", "clock_gettime", cexe) | |
| cmd = testenv.CleanCmdEnv(cmd) | |
| out, err = cmd.CombinedOutput() | |
| if len(out) > 0 { | |
| t.Logf("%s", out) | |
| } | |
| if err != nil { | |
| t.Skipf("can't verify VDSO status, C program failed: %v", err) | |
| } | |
| if cgot := bytes.Count(out, []byte("gettime")); cgot >= 100 { | |
| t.Logf("found %d gettime calls, want < %d", cgot, 100) | |
| t.Log("C program does not use VDSO either") | |
| return | |
| } | |
| // The Go program used the system call but the C | |
| // program did not. This is a VDSO failure for Go. | |
| t.Errorf("did not use VDSO system call") | |
| } | |
| } | |
| const vdsoCProgram = ` | |
| #include <stdio.h> | |
| #include <time.h> | |
| int main() { | |
| int i; | |
| time_t tot; | |
| for (i = 0; i < 100; i++) { | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| tot += ts.tv_nsec; | |
| } | |
| printf("%d\n", (int)(tot)); | |
| return 0; | |
| } | |
| ` |