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 TestWindowsBuildmodeCSharedTrailingDotOutput(t *testing.T) {
   203  	if runtime.GOOS != "windows" {
   204  		t.Skip("skipping windows only test")
   205  	}
   206  
   207  	t.Parallel()
   208  	testenv.MustHaveGoBuild(t)
   209  	testenv.MustHaveCGO(t)
   210  	testenv.MustHaveBuildMode(t, "c-shared")
   211  
   212  	dir := t.TempDir()
   213  	srcfile := filepath.Join(dir, "test.go")
   214  	objfile := filepath.Join(dir, "mypackage.")
   215  	linktmp := filepath.Join(dir, "linktmp")
   216  	if err := os.Mkdir(linktmp, 0777); err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	if err := os.WriteFile(srcfile, []byte(`package main
   220  import "C"
   221  
   222  //export Hello
   223  func Hello() {}
   224  
   225  func main() {}
   226  `), 0666); err != nil {
   227  		t.Fatal(err)
   228  	}
   229  
   230  	argv := []string{"build", "-buildmode=c-shared", "-o", objfile, "-ldflags", "-tmpdir=" + linktmp, srcfile}
   231  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   232  	if err != nil {
   233  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   234  	}
   235  
   236  	def, err := os.ReadFile(filepath.Join(linktmp, "export_file.def"))
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	if want := []byte("LIBRARY \"mypackage.\"\n"); !bytes.HasPrefix(def, want) {
   241  		t.Fatalf("export_file.def begins with %q, want %q", def, want)
   242  	}
   243  }
   244  
   245  func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) {
   246  	t.Parallel()
   247  	testenv.MustHaveGoBuild(t)
   248  
   249  	dir := t.TempDir()
   250  
   251  	srcfile := filepath.Join(dir, "test.go")
   252  	objfile := filepath.Join(dir, "test.dll")
   253  	if err := os.WriteFile(srcfile, []byte(`package main; func main() { print("hello") }`), 0666); err != nil {
   254  		t.Fatal(err)
   255  	}
   256  	argv := []string{"build", "-buildmode=c-shared"}
   257  	if !useASLR {
   258  		argv = append(argv, "-ldflags", "-aslr=false")
   259  	}
   260  	argv = append(argv, "-o", objfile, srcfile)
   261  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   262  	if err != nil {
   263  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   264  	}
   265  
   266  	f, err := pe.Open(objfile)
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	defer f.Close()
   271  	var dc uint16
   272  	switch oh := f.OptionalHeader.(type) {
   273  	case *pe.OptionalHeader32:
   274  		dc = oh.DllCharacteristics
   275  	case *pe.OptionalHeader64:
   276  		dc = oh.DllCharacteristics
   277  		hasHEVA := (dc & pe.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) != 0
   278  		if useASLR && !hasHEVA {
   279  			t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set")
   280  		} else if !useASLR && hasHEVA {
   281  			t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag should not be set")
   282  		}
   283  	default:
   284  		t.Fatalf("unexpected optional header type of %T", f.OptionalHeader)
   285  	}
   286  	hasASLR := (dc & pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) != 0
   287  	if useASLR && !hasASLR {
   288  		t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set")
   289  	} else if !useASLR && hasASLR {
   290  		t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag should not be set")
   291  	}
   292  }
   293  
   294  // TestMemProfileCheck tests that cmd/link sets
   295  // runtime.disableMemoryProfiling if the runtime.MemProfile
   296  // symbol is unreachable after deadcode (and not dynlinking).
   297  // The runtime then uses that to set the default value of
   298  // runtime.MemProfileRate, which this test checks.
   299  func TestMemProfileCheck(t *testing.T) {
   300  	testenv.MustHaveGoBuild(t)
   301  	t.Parallel()
   302  
   303  	tests := []struct {
   304  		name    string
   305  		prog    string
   306  		wantOut string
   307  	}{
   308  		{
   309  			"no_memprofile",
   310  			`
   311  package main
   312  import "runtime"
   313  func main() {
   314  	println(runtime.MemProfileRate)
   315  }
   316  `,
   317  			"0",
   318  		},
   319  		{
   320  			"with_memprofile",
   321  			`
   322  package main
   323  import "runtime"
   324  func main() {
   325  	runtime.MemProfile(nil, false)
   326  	println(runtime.MemProfileRate)
   327  }
   328  `,
   329  			"524288",
   330  		},
   331  		{
   332  			"with_memprofile_indirect",
   333  			`
   334  package main
   335  import "runtime"
   336  var f = runtime.MemProfile
   337  func main() {
   338  	if f == nil {
   339  		panic("no f")
   340  	}
   341  	println(runtime.MemProfileRate)
   342  }
   343  `,
   344  			"524288",
   345  		},
   346  		{
   347  			"with_memprofile_runtime_pprof",
   348  			`
   349  package main
   350  import "runtime"
   351  import "runtime/pprof"
   352  func main() {
   353  	_ = pprof.Profiles()
   354  	println(runtime.MemProfileRate)
   355  }
   356  `,
   357  			"524288",
   358  		},
   359  		{
   360  			"with_memprofile_runtime_pprof_writeheap",
   361  			`
   362  package main
   363  import "io"
   364  import "runtime"
   365  import "runtime/pprof"
   366  func main() {
   367  	_ = pprof.WriteHeapProfile(io.Discard)
   368  	println(runtime.MemProfileRate)
   369  }
   370  `,
   371  			"524288",
   372  		},
   373  		{
   374  			"with_memprofile_runtime_pprof_lookupheap",
   375  			`
   376  package main
   377  import "runtime"
   378  import "runtime/pprof"
   379  func main() {
   380  	_ = pprof.Lookup("heap")
   381  	println(runtime.MemProfileRate)
   382  }
   383  `,
   384  			"524288",
   385  		},
   386  		{
   387  			"with_memprofile_http_pprof",
   388  			`
   389  package main
   390  import "runtime"
   391  import _ "net/http/pprof"
   392  func main() {
   393  	println(runtime.MemProfileRate)
   394  }
   395  `,
   396  			"524288",
   397  		},
   398  	}
   399  	for _, tt := range tests {
   400  		tt := tt
   401  		t.Run(tt.name, func(t *testing.T) {
   402  			t.Parallel()
   403  			tempDir := t.TempDir()
   404  			src := filepath.Join(tempDir, "x.go")
   405  			if err := os.WriteFile(src, []byte(tt.prog), 0644); err != nil {
   406  				t.Fatal(err)
   407  			}
   408  			cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src)
   409  			out, err := cmd.CombinedOutput()
   410  			if err != nil {
   411  				t.Fatal(err)
   412  			}
   413  			got := strings.TrimSpace(string(out))
   414  			if got != tt.wantOut {
   415  				t.Errorf("got %q; want %q", got, tt.wantOut)
   416  			}
   417  		})
   418  	}
   419  }
   420  
   421  func TestRISCVTrampolines(t *testing.T) {
   422  	testenv.MustHaveGoBuild(t)
   423  	t.Parallel()
   424  
   425  	tmpDir := t.TempDir()
   426  	tmpFile := filepath.Join(tmpDir, "x.s")
   427  
   428  	// Calling b from a or c should not use trampolines, however
   429  	// calling from d to a will require one.
   430  	buf := new(bytes.Buffer)
   431  	fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
   432  	for i := 0; i < 1<<17; i++ {
   433  		fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
   434  	}
   435  	fmt.Fprintf(buf, "\tCALL b(SB)\n")
   436  	fmt.Fprintf(buf, "\tRET\n")
   437  	fmt.Fprintf(buf, "TEXT b(SB),$0-0\n")
   438  	fmt.Fprintf(buf, "\tRET\n")
   439  	fmt.Fprintf(buf, "TEXT c(SB),$0-0\n")
   440  	fmt.Fprintf(buf, "\tCALL b(SB)\n")
   441  	fmt.Fprintf(buf, "\tRET\n")
   442  	fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
   443  	for i := 0; i < 1<<17; i++ {
   444  		fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
   445  	}
   446  	fmt.Fprintf(buf, "\tCALL a(SB)\n")
   447  	fmt.Fprintf(buf, "\tCALL c(SB)\n")
   448  	fmt.Fprintf(buf, "\tRET\n")
   449  	if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil {
   450  		t.Fatalf("Failed to write assembly file: %v", err)
   451  	}
   452  
   453  	if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil {
   454  		t.Fatalf("Failed to write file: %v\n", err)
   455  	}
   456  	main := `package main
   457  func main() {
   458  	d()
   459  }
   460  
   461  func d()
   462  `
   463  	if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil {
   464  		t.Fatalf("failed to write main: %v\n", err)
   465  	}
   466  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
   467  	cmd.Dir = tmpDir
   468  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   469  	out, err := cmd.CombinedOutput()
   470  	if err != nil {
   471  		t.Fatalf("Build failed: %v, output: %s", err, out)
   472  	}
   473  
   474  	// Check what trampolines exist.
   475  	cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp"))
   476  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   477  	out, err = cmd.CombinedOutput()
   478  	if err != nil {
   479  		t.Fatalf("nm failure: %s\n%s\n", err, string(out))
   480  	}
   481  	if !bytes.Contains(out, []byte(" T a-tramp0")) {
   482  		t.Errorf("Trampoline a-tramp0 is missing")
   483  	}
   484  	if bytes.Contains(out, []byte(" T b-tramp0")) {
   485  		t.Errorf("Trampoline b-tramp0 exists unnecessarily")
   486  	}
   487  }
   488  
   489  func TestRounding(t *testing.T) {
   490  	testCases := []struct {
   491  		input    int64
   492  		quantum  int64
   493  		expected int64
   494  	}{
   495  		{0x30000000, 0x2000, 0x30000000}, // Already aligned
   496  		{0x30002000, 0x2000, 0x30002000}, // Exactly on boundary
   497  		{0x30001234, 0x2000, 0x30002000},
   498  		{0x30001000, 0x2000, 0x30002000},
   499  		{0x30001fff, 0x2000, 0x30002000},
   500  	}
   501  
   502  	for _, tc := range testCases {
   503  		result := Rnd(tc.input, tc.quantum)
   504  		if result != tc.expected {
   505  			t.Errorf("Rnd(0x%x, 0x%x) = 0x%x, expected 0x%x",
   506  				tc.input, tc.quantum, result, tc.expected)
   507  		}
   508  	}
   509  }
   510  

View as plain text