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

View as plain text