Source file src/cmd/link/internal/ld/ld_test.go

     1  // Copyright 2018 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  package ld
     6  
     7  import (
     8  	"bytes"
     9  	"debug/pe"
    10  	"fmt"
    11  	"internal/testenv"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  func TestUndefinedRelocErrors(t *testing.T) {
    20  	testenv.MustHaveGoBuild(t)
    21  
    22  	// When external linking, symbols may be defined externally, so we allow
    23  	// undefined symbols and let external linker resolve. Skip the test.
    24  	//
    25  	// N.B. go build below explictly doesn't pass through
    26  	// -asan/-msan/-race, so we don't care about those.
    27  	testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
    28  
    29  	t.Parallel()
    30  
    31  	out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "./testdata/issue10978").CombinedOutput()
    32  	if err == nil {
    33  		t.Fatal("expected build to fail")
    34  	}
    35  
    36  	wantErrors := map[string]int{
    37  		// Main function has dedicated error message.
    38  		"function main is undeclared in the main package": 1,
    39  
    40  		// Single error reporting per each symbol.
    41  		// This way, duplicated messages are not reported for
    42  		// multiple relocations with a same name.
    43  		"main.defined1: relocation target main.undefined not defined": 1,
    44  		"main.defined2: relocation target main.undefined not defined": 1,
    45  	}
    46  	unexpectedErrors := map[string]int{}
    47  
    48  	for _, l := range strings.Split(string(out), "\n") {
    49  		if strings.HasPrefix(l, "#") || l == "" {
    50  			continue
    51  		}
    52  		matched := ""
    53  		for want := range wantErrors {
    54  			if strings.Contains(l, want) {
    55  				matched = want
    56  				break
    57  			}
    58  		}
    59  		if matched != "" {
    60  			wantErrors[matched]--
    61  		} else {
    62  			unexpectedErrors[l]++
    63  		}
    64  	}
    65  
    66  	for want, n := range wantErrors {
    67  		switch {
    68  		case n > 0:
    69  			t.Errorf("unmatched error: %s (x%d)", want, n)
    70  		case n < 0:
    71  			if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
    72  				testenv.SkipFlaky(t, 58807)
    73  			}
    74  			t.Errorf("extra errors: %s (x%d)", want, -n)
    75  		}
    76  	}
    77  	for unexpected, n := range unexpectedErrors {
    78  		t.Errorf("unexpected error: %s (x%d)", unexpected, n)
    79  	}
    80  }
    81  
    82  const carchiveSrcText = `
    83  package main
    84  
    85  //export GoFunc
    86  func GoFunc() {
    87  	println(42)
    88  }
    89  
    90  func main() {
    91  }
    92  `
    93  
    94  func TestArchiveBuildInvokeWithExec(t *testing.T) {
    95  	t.Parallel()
    96  	testenv.MustHaveGoBuild(t)
    97  	testenv.MustHaveCGO(t)
    98  
    99  	// run this test on just a small set of platforms (no need to test it
   100  	// across the board given the nature of the test).
   101  	pair := runtime.GOOS + "-" + runtime.GOARCH
   102  	switch pair {
   103  	case "darwin-amd64", "darwin-arm64", "linux-amd64", "freebsd-amd64":
   104  	default:
   105  		t.Skip("no need for test on " + pair)
   106  	}
   107  	switch runtime.GOOS {
   108  	case "openbsd", "windows":
   109  		t.Skip("c-archive unsupported")
   110  	}
   111  	dir := t.TempDir()
   112  
   113  	srcfile := filepath.Join(dir, "test.go")
   114  	arfile := filepath.Join(dir, "test.a")
   115  	if err := os.WriteFile(srcfile, []byte(carchiveSrcText), 0666); err != nil {
   116  		t.Fatal(err)
   117  	}
   118  
   119  	ldf := fmt.Sprintf("-ldflags=-v -tmpdir=%s", dir)
   120  	argv := []string{"build", "-buildmode=c-archive", "-o", arfile, ldf, srcfile}
   121  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   122  	if err != nil {
   123  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   124  	}
   125  
   126  	found := false
   127  	const want = "invoking archiver with syscall.Exec"
   128  	for _, l := range strings.Split(string(out), "\n") {
   129  		if strings.HasPrefix(l, want) {
   130  			found = true
   131  			break
   132  		}
   133  	}
   134  
   135  	if !found {
   136  		t.Errorf("expected '%s' in -v output, got:\n%s\n", want, string(out))
   137  	}
   138  }
   139  
   140  func TestLargeTextSectionSplitting(t *testing.T) {
   141  	switch runtime.GOARCH {
   142  	case "ppc64", "ppc64le", "arm":
   143  	case "arm64":
   144  		if runtime.GOOS == "darwin" {
   145  			break
   146  		}
   147  		fallthrough
   148  	default:
   149  		t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
   150  	}
   151  
   152  	testenv.MustHaveGoBuild(t)
   153  	testenv.MustHaveCGO(t)
   154  	t.Parallel()
   155  	dir := t.TempDir()
   156  
   157  	// NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to
   158  	// split text sections at a size threshold of 1M instead of the
   159  	// architected limit of 67M or larger. The choice of building cmd/go
   160  	// is arbitrary; we just need something sufficiently large that uses
   161  	// external linking.
   162  	exe := filepath.Join(dir, "go.exe")
   163  	out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput()
   164  	if err != nil {
   165  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   166  	}
   167  
   168  	// Check that we did split text sections.
   169  	out, err = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe).CombinedOutput()
   170  	if err != nil {
   171  		t.Fatalf("nm failure: %s\n%s\n", err, string(out))
   172  	}
   173  	if !bytes.Contains(out, []byte("runtime.text.1")) {
   174  		t.Errorf("runtime.text.1 not found, text section not split?")
   175  	}
   176  
   177  	// Result should be runnable.
   178  	_, err = testenv.Command(t, exe, "version").CombinedOutput()
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  }
   183  
   184  func TestWindowsBuildmodeCSharedASLR(t *testing.T) {
   185  	platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
   186  	switch platform {
   187  	case "windows/amd64", "windows/386":
   188  	default:
   189  		t.Skip("skipping windows amd64/386 only test")
   190  	}
   191  
   192  	testenv.MustHaveCGO(t)
   193  
   194  	t.Run("aslr", func(t *testing.T) {
   195  		testWindowsBuildmodeCSharedASLR(t, true)
   196  	})
   197  	t.Run("no-aslr", func(t *testing.T) {
   198  		testWindowsBuildmodeCSharedASLR(t, false)
   199  	})
   200  }
   201  
   202  func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) {
   203  	t.Parallel()
   204  	testenv.MustHaveGoBuild(t)
   205  
   206  	dir := t.TempDir()
   207  
   208  	srcfile := filepath.Join(dir, "test.go")
   209  	objfile := filepath.Join(dir, "test.dll")
   210  	if err := os.WriteFile(srcfile, []byte(`package main; func main() { print("hello") }`), 0666); err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	argv := []string{"build", "-buildmode=c-shared"}
   214  	if !useASLR {
   215  		argv = append(argv, "-ldflags", "-aslr=false")
   216  	}
   217  	argv = append(argv, "-o", objfile, srcfile)
   218  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   219  	if err != nil {
   220  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   221  	}
   222  
   223  	f, err := pe.Open(objfile)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	defer f.Close()
   228  	var dc uint16
   229  	switch oh := f.OptionalHeader.(type) {
   230  	case *pe.OptionalHeader32:
   231  		dc = oh.DllCharacteristics
   232  	case *pe.OptionalHeader64:
   233  		dc = oh.DllCharacteristics
   234  		hasHEVA := (dc & pe.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) != 0
   235  		if useASLR && !hasHEVA {
   236  			t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set")
   237  		} else if !useASLR && hasHEVA {
   238  			t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag should not be set")
   239  		}
   240  	default:
   241  		t.Fatalf("unexpected optional header type of %T", f.OptionalHeader)
   242  	}
   243  	hasASLR := (dc & pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) != 0
   244  	if useASLR && !hasASLR {
   245  		t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set")
   246  	} else if !useASLR && hasASLR {
   247  		t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag should not be set")
   248  	}
   249  }
   250  
   251  // TestMemProfileCheck tests that cmd/link sets
   252  // runtime.disableMemoryProfiling if the runtime.MemProfile
   253  // symbol is unreachable after deadcode (and not dynlinking).
   254  // The runtime then uses that to set the default value of
   255  // runtime.MemProfileRate, which this test checks.
   256  func TestMemProfileCheck(t *testing.T) {
   257  	testenv.MustHaveGoBuild(t)
   258  	t.Parallel()
   259  
   260  	tests := []struct {
   261  		name    string
   262  		prog    string
   263  		wantOut string
   264  	}{
   265  		{
   266  			"no_memprofile",
   267  			`
   268  package main
   269  import "runtime"
   270  func main() {
   271  	println(runtime.MemProfileRate)
   272  }
   273  `,
   274  			"0",
   275  		},
   276  		{
   277  			"with_memprofile",
   278  			`
   279  package main
   280  import "runtime"
   281  func main() {
   282  	runtime.MemProfile(nil, false)
   283  	println(runtime.MemProfileRate)
   284  }
   285  `,
   286  			"524288",
   287  		},
   288  		{
   289  			"with_memprofile_indirect",
   290  			`
   291  package main
   292  import "runtime"
   293  var f = runtime.MemProfile
   294  func main() {
   295  	if f == nil {
   296  		panic("no f")
   297  	}
   298  	println(runtime.MemProfileRate)
   299  }
   300  `,
   301  			"524288",
   302  		},
   303  		{
   304  			"with_memprofile_runtime_pprof",
   305  			`
   306  package main
   307  import "runtime"
   308  import "runtime/pprof"
   309  func main() {
   310  	_ = pprof.Profiles()
   311  	println(runtime.MemProfileRate)
   312  }
   313  `,
   314  			"524288",
   315  		},
   316  		{
   317  			"with_memprofile_runtime_pprof_writeheap",
   318  			`
   319  package main
   320  import "io"
   321  import "runtime"
   322  import "runtime/pprof"
   323  func main() {
   324  	_ = pprof.WriteHeapProfile(io.Discard)
   325  	println(runtime.MemProfileRate)
   326  }
   327  `,
   328  			"524288",
   329  		},
   330  		{
   331  			"with_memprofile_runtime_pprof_lookupheap",
   332  			`
   333  package main
   334  import "runtime"
   335  import "runtime/pprof"
   336  func main() {
   337  	_ = pprof.Lookup("heap")
   338  	println(runtime.MemProfileRate)
   339  }
   340  `,
   341  			"524288",
   342  		},
   343  		{
   344  			"with_memprofile_http_pprof",
   345  			`
   346  package main
   347  import "runtime"
   348  import _ "net/http/pprof"
   349  func main() {
   350  	println(runtime.MemProfileRate)
   351  }
   352  `,
   353  			"524288",
   354  		},
   355  	}
   356  	for _, tt := range tests {
   357  		tt := tt
   358  		t.Run(tt.name, func(t *testing.T) {
   359  			t.Parallel()
   360  			tempDir := t.TempDir()
   361  			src := filepath.Join(tempDir, "x.go")
   362  			if err := os.WriteFile(src, []byte(tt.prog), 0644); err != nil {
   363  				t.Fatal(err)
   364  			}
   365  			cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src)
   366  			out, err := cmd.CombinedOutput()
   367  			if err != nil {
   368  				t.Fatal(err)
   369  			}
   370  			got := strings.TrimSpace(string(out))
   371  			if got != tt.wantOut {
   372  				t.Errorf("got %q; want %q", got, tt.wantOut)
   373  			}
   374  		})
   375  	}
   376  }
   377  
   378  func TestRISCVTrampolines(t *testing.T) {
   379  	testenv.MustHaveGoBuild(t)
   380  	t.Parallel()
   381  
   382  	tmpDir := t.TempDir()
   383  	tmpFile := filepath.Join(tmpDir, "x.s")
   384  
   385  	// Calling b from a or c should not use trampolines, however
   386  	// calling from d to a will require one.
   387  	buf := new(bytes.Buffer)
   388  	fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
   389  	for i := 0; i < 1<<17; i++ {
   390  		fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
   391  	}
   392  	fmt.Fprintf(buf, "\tCALL b(SB)\n")
   393  	fmt.Fprintf(buf, "\tRET\n")
   394  	fmt.Fprintf(buf, "TEXT b(SB),$0-0\n")
   395  	fmt.Fprintf(buf, "\tRET\n")
   396  	fmt.Fprintf(buf, "TEXT c(SB),$0-0\n")
   397  	fmt.Fprintf(buf, "\tCALL b(SB)\n")
   398  	fmt.Fprintf(buf, "\tRET\n")
   399  	fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
   400  	for i := 0; i < 1<<17; i++ {
   401  		fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
   402  	}
   403  	fmt.Fprintf(buf, "\tCALL a(SB)\n")
   404  	fmt.Fprintf(buf, "\tCALL c(SB)\n")
   405  	fmt.Fprintf(buf, "\tRET\n")
   406  	if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil {
   407  		t.Fatalf("Failed to write assembly file: %v", err)
   408  	}
   409  
   410  	if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil {
   411  		t.Fatalf("Failed to write file: %v\n", err)
   412  	}
   413  	main := `package main
   414  func main() {
   415  	d()
   416  }
   417  
   418  func d()
   419  `
   420  	if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil {
   421  		t.Fatalf("failed to write main: %v\n", err)
   422  	}
   423  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
   424  	cmd.Dir = tmpDir
   425  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   426  	out, err := cmd.CombinedOutput()
   427  	if err != nil {
   428  		t.Fatalf("Build failed: %v, output: %s", err, out)
   429  	}
   430  
   431  	// Check what trampolines exist.
   432  	cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp"))
   433  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   434  	out, err = cmd.CombinedOutput()
   435  	if err != nil {
   436  		t.Fatalf("nm failure: %s\n%s\n", err, string(out))
   437  	}
   438  	if !bytes.Contains(out, []byte(" T a-tramp0")) {
   439  		t.Errorf("Trampoline a-tramp0 is missing")
   440  	}
   441  	if bytes.Contains(out, []byte(" T b-tramp0")) {
   442  		t.Errorf("Trampoline b-tramp0 exists unnecessarily")
   443  	}
   444  }
   445  

View as plain text