1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build (freebsd && (386 || amd64 || arm || arm64 || riscv64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || mips64 || mips64le || ppc64 || ppc64le || riscv64 || s390x))
6
7package runtime_test
8
9import (
10	"bytes"
11	"internal/testenv"
12	"os"
13	"os/exec"
14	"path/filepath"
15	"syscall"
16	"testing"
17	"time"
18)
19
20// TestUsingVDSO tests that we are actually using the VDSO to fetch
21// the time.
22func TestUsingVDSO(t *testing.T) {
23	const calls = 100
24
25	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
26		// Fetch the time a lot.
27		var total int64
28		for i := 0; i < calls; i++ {
29			total += time.Now().UnixNano()
30		}
31		os.Exit(0)
32	}
33
34	t.Parallel()
35
36	// Look for strace in /bin or /usr/bin. Don't assume that some
37	// strace on PATH is the one that we want.
38	strace := "/bin/strace"
39	if _, err := os.Stat(strace); err != nil {
40		strace = "/usr/bin/strace"
41		if _, err := os.Stat(strace); err != nil {
42			t.Skipf("skipping test because strace not found: %v", err)
43		}
44	}
45
46	exe, err := os.Executable()
47	if err != nil {
48		t.Skipf("skipping because Executable failed: %v", err)
49	}
50
51	t.Logf("GO_WANT_HELPER_PROCESS=1 %s -f -e clock_gettime %s -test.run=^TestUsingVDSO$", strace, exe)
52	cmd := testenv.Command(t, strace, "-f", "-e", "clock_gettime", exe, "-test.run=^TestUsingVDSO$")
53	cmd = testenv.CleanCmdEnv(cmd)
54	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
55	out, err := cmd.CombinedOutput()
56	if len(out) > 0 {
57		t.Logf("%s", out)
58	}
59	if err != nil {
60		if err := err.(*exec.ExitError); err != nil && err.Sys().(syscall.WaitStatus).Signaled() {
61			if !bytes.Contains(out, []byte("+++ killed by")) {
62				// strace itself occasionally crashes.
63				// Here, it exited with a signal, but
64				// the strace log didn't report any
65				// signal from the child process.
66				t.Log(err)
67				testenv.SkipFlaky(t, 63734)
68			}
69		}
70		t.Fatal(err)
71	}
72
73	if got := bytes.Count(out, []byte("gettime")); got >= calls {
74		t.Logf("found %d gettime calls, want < %d", got, calls)
75
76		// Try to double-check that a C program uses the VDSO.
77		tempdir := t.TempDir()
78		cfn := filepath.Join(tempdir, "time.c")
79		cexe := filepath.Join(tempdir, "time")
80		if err := os.WriteFile(cfn, []byte(vdsoCProgram), 0o644); err != nil {
81			t.Fatal(err)
82		}
83		cc := os.Getenv("CC")
84		if cc == "" {
85			cc, err = exec.LookPath("gcc")
86			if err != nil {
87				cc, err = exec.LookPath("clang")
88				if err != nil {
89					t.Skip("can't verify VDSO status, no C compiler")
90				}
91			}
92		}
93
94		t.Logf("%s -o %s %s", cc, cexe, cfn)
95		cmd = testenv.Command(t, cc, "-o", cexe, cfn)
96		cmd = testenv.CleanCmdEnv(cmd)
97		out, err = cmd.CombinedOutput()
98		if len(out) > 0 {
99			t.Logf("%s", out)
100		}
101		if err != nil {
102			t.Skipf("can't verify VDSO status, C compiled failed: %v", err)
103		}
104
105		t.Logf("%s -f -e clock_gettime %s", strace, cexe)
106		cmd = testenv.Command(t, strace, "-f", "-e", "clock_gettime", cexe)
107		cmd = testenv.CleanCmdEnv(cmd)
108		out, err = cmd.CombinedOutput()
109		if len(out) > 0 {
110			t.Logf("%s", out)
111		}
112		if err != nil {
113			t.Skipf("can't verify VDSO status, C program failed: %v", err)
114		}
115
116		if cgot := bytes.Count(out, []byte("gettime")); cgot >= 100 {
117			t.Logf("found %d gettime calls, want < %d", cgot, 100)
118			t.Log("C program does not use VDSO either")
119			return
120		}
121
122		// The Go program used the system call but the C
123		// program did not. This is a VDSO failure for Go.
124		t.Errorf("did not use VDSO system call")
125	}
126}
127
128const vdsoCProgram = `
129#include <stdio.h>
130#include <time.h>
131
132int main() {
133	int i;
134	time_t tot;
135	for (i = 0; i < 100; i++) {
136		struct timespec ts;
137		clock_gettime(CLOCK_MONOTONIC, &ts);
138		tot += ts.tv_nsec;
139	}
140	printf("%d\n", (int)(tot));
141	return 0;
142}
143`
144