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

View as plain text