• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5package ld
6
7import (
8	"bytes"
9	"debug/pe"
10	"fmt"
11	"internal/testenv"
12	"os"
13	"path/filepath"
14	"runtime"
15	"strings"
16	"testing"
17)
18
19func 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
79const carchiveSrcText = `
80package main
81
82//export GoFunc
83func GoFunc() {
84	println(42)
85}
86
87func main() {
88}
89`
90
91func 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
137func 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
181func 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
199func 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.
253func 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			`
265package main
266import "runtime"
267func main() {
268	println(runtime.MemProfileRate)
269}
270`,
271			"0",
272		},
273		{
274			"with_memprofile",
275			`
276package main
277import "runtime"
278func main() {
279	runtime.MemProfile(nil, false)
280	println(runtime.MemProfileRate)
281}
282`,
283			"524288",
284		},
285		{
286			"with_memprofile_indirect",
287			`
288package main
289import "runtime"
290var f = runtime.MemProfile
291func 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			`
303package main
304import "runtime"
305import "runtime/pprof"
306func main() {
307	_ = pprof.Profiles()
308	println(runtime.MemProfileRate)
309}
310`,
311			"524288",
312		},
313		{
314			"with_memprofile_runtime_pprof_writeheap",
315			`
316package main
317import "io"
318import "runtime"
319import "runtime/pprof"
320func main() {
321	_ = pprof.WriteHeapProfile(io.Discard)
322	println(runtime.MemProfileRate)
323}
324`,
325			"524288",
326		},
327		{
328			"with_memprofile_runtime_pprof_lookupheap",
329			`
330package main
331import "runtime"
332import "runtime/pprof"
333func main() {
334	_ = pprof.Lookup("heap")
335	println(runtime.MemProfileRate)
336}
337`,
338			"524288",
339		},
340		{
341			"with_memprofile_http_pprof",
342			`
343package main
344import "runtime"
345import _ "net/http/pprof"
346func main() {
347	println(runtime.MemProfileRate)
348}
349`,
350			"524288",
351		},
352	}
353	for _, tt := range tests {
354		tt := tt
355		t.Run(tt.name, func(t *testing.T) {
356			t.Parallel()
357			tempDir := t.TempDir()
358			src := filepath.Join(tempDir, "x.go")
359			if err := os.WriteFile(src, []byte(tt.prog), 0644); err != nil {
360				t.Fatal(err)
361			}
362			cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src)
363			out, err := cmd.CombinedOutput()
364			if err != nil {
365				t.Fatal(err)
366			}
367			got := strings.TrimSpace(string(out))
368			if got != tt.wantOut {
369				t.Errorf("got %q; want %q", got, tt.wantOut)
370			}
371		})
372	}
373}
374
375func TestRISCVTrampolines(t *testing.T) {
376	testenv.MustHaveGoBuild(t)
377	t.Parallel()
378
379	tmpDir := t.TempDir()
380	tmpFile := filepath.Join(tmpDir, "x.s")
381
382	// Calling b from a or c should not use trampolines, however
383	// calling from d to a will require one.
384	buf := new(bytes.Buffer)
385	fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
386	for i := 0; i < 1<<17; i++ {
387		fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
388	}
389	fmt.Fprintf(buf, "\tCALL b(SB)\n")
390	fmt.Fprintf(buf, "\tRET\n")
391	fmt.Fprintf(buf, "TEXT b(SB),$0-0\n")
392	fmt.Fprintf(buf, "\tRET\n")
393	fmt.Fprintf(buf, "TEXT c(SB),$0-0\n")
394	fmt.Fprintf(buf, "\tCALL b(SB)\n")
395	fmt.Fprintf(buf, "\tRET\n")
396	fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
397	for i := 0; i < 1<<17; i++ {
398		fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
399	}
400	fmt.Fprintf(buf, "\tCALL a(SB)\n")
401	fmt.Fprintf(buf, "\tCALL c(SB)\n")
402	fmt.Fprintf(buf, "\tRET\n")
403	if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil {
404		t.Fatalf("Failed to write assembly file: %v", err)
405	}
406
407	if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil {
408		t.Fatalf("Failed to write file: %v\n", err)
409	}
410	main := `package main
411func main() {
412	d()
413}
414
415func d()
416`
417	if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil {
418		t.Fatalf("failed to write main: %v\n", err)
419	}
420	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
421	cmd.Dir = tmpDir
422	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
423	out, err := cmd.CombinedOutput()
424	if err != nil {
425		t.Fatalf("Build failed: %v, output: %s", err, out)
426	}
427
428	// Check what trampolines exist.
429	cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp"))
430	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
431	out, err = cmd.CombinedOutput()
432	if err != nil {
433		t.Fatalf("nm failure: %s\n%s\n", err, string(out))
434	}
435	if !bytes.Contains(out, []byte(" T a-tramp0")) {
436		t.Errorf("Trampoline a-tramp0 is missing")
437	}
438	if bytes.Contains(out, []byte(" T b-tramp0")) {
439		t.Errorf("Trampoline b-tramp0 exists unnecessarily")
440	}
441}
442