Source file src/runtime/vdso_test.go

     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  
     7  package runtime_test
     8  
     9  import (
    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.
    22  func 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  
   128  const vdsoCProgram = `
   129  #include <stdio.h>
   130  #include <time.h>
   131  
   132  int 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  

View as plain text