1// Copyright 2019 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//go:build dragonfly || freebsd || linux || netbsd || openbsd
6
7package main
8
9import (
10	"bytes"
11	"cmd/internal/buildid"
12	"cmd/internal/notsha256"
13	"cmd/link/internal/ld"
14	"debug/elf"
15	"fmt"
16	"internal/platform"
17	"internal/testenv"
18	"os"
19	"os/exec"
20	"path/filepath"
21	"runtime"
22	"strings"
23	"sync"
24	"testing"
25	"text/template"
26)
27
28func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) {
29	goTool := testenv.GoToolPath(t)
30	cmd := testenv.Command(t, goTool, "env", "CC")
31	cmd.Env = env
32	ccb, err := cmd.Output()
33	if err != nil {
34		t.Fatal(err)
35	}
36	cc := strings.TrimSpace(string(ccb))
37
38	cmd = testenv.Command(t, goTool, "env", "GOGCCFLAGS")
39	cmd.Env = env
40	cflagsb, err := cmd.Output()
41	if err != nil {
42		t.Fatal(err)
43	}
44	cflags := strings.Fields(string(cflagsb))
45
46	return cc, cflags
47}
48
49var asmSource = `
50	.section .text1,"ax"
51s1:
52	.byte 0
53	.section .text2,"ax"
54s2:
55	.byte 0
56`
57
58var goSource = `
59package main
60func main() {}
61`
62
63// The linker used to crash if an ELF input file had multiple text sections
64// with the same name.
65func TestSectionsWithSameName(t *testing.T) {
66	testenv.MustHaveGoBuild(t)
67	testenv.MustHaveCGO(t)
68	t.Parallel()
69
70	objcopy, err := exec.LookPath("objcopy")
71	if err != nil {
72		t.Skipf("can't find objcopy: %v", err)
73	}
74
75	dir := t.TempDir()
76
77	gopath := filepath.Join(dir, "GOPATH")
78	env := append(os.Environ(), "GOPATH="+gopath)
79
80	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
81		t.Fatal(err)
82	}
83
84	asmFile := filepath.Join(dir, "x.s")
85	if err := os.WriteFile(asmFile, []byte(asmSource), 0444); err != nil {
86		t.Fatal(err)
87	}
88
89	goTool := testenv.GoToolPath(t)
90	cc, cflags := getCCAndCCFLAGS(t, env)
91
92	asmObj := filepath.Join(dir, "x.o")
93	t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile)
94	if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", asmObj, asmFile)...).CombinedOutput(); err != nil {
95		t.Logf("%s", out)
96		t.Fatal(err)
97	}
98
99	asm2Obj := filepath.Join(dir, "x2.syso")
100	t.Logf("%s --rename-section .text2=.text1 %s %s", objcopy, asmObj, asm2Obj)
101	if out, err := testenv.Command(t, objcopy, "--rename-section", ".text2=.text1", asmObj, asm2Obj).CombinedOutput(); err != nil {
102		t.Logf("%s", out)
103		t.Fatal(err)
104	}
105
106	for _, s := range []string{asmFile, asmObj} {
107		if err := os.Remove(s); err != nil {
108			t.Fatal(err)
109		}
110	}
111
112	goFile := filepath.Join(dir, "main.go")
113	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
114		t.Fatal(err)
115	}
116
117	cmd := testenv.Command(t, goTool, "build")
118	cmd.Dir = dir
119	cmd.Env = env
120	t.Logf("%s build", goTool)
121	if out, err := cmd.CombinedOutput(); err != nil {
122		t.Logf("%s", out)
123		t.Fatal(err)
124	}
125}
126
127var cSources35779 = []string{`
128static int blah() { return 42; }
129int Cfunc1() { return blah(); }
130`, `
131static int blah() { return 42; }
132int Cfunc2() { return blah(); }
133`,
134}
135
136// TestMinusRSymsWithSameName tests a corner case in the new
137// loader. Prior to the fix this failed with the error 'loadelf:
138// $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in
139// both main(.text) and main(.text)'. See issue #35779.
140func TestMinusRSymsWithSameName(t *testing.T) {
141	testenv.MustHaveGoBuild(t)
142	testenv.MustHaveCGO(t)
143	t.Parallel()
144
145	dir := t.TempDir()
146
147	gopath := filepath.Join(dir, "GOPATH")
148	env := append(os.Environ(), "GOPATH="+gopath)
149
150	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
151		t.Fatal(err)
152	}
153
154	goTool := testenv.GoToolPath(t)
155	cc, cflags := getCCAndCCFLAGS(t, env)
156
157	objs := []string{}
158	csrcs := []string{}
159	for i, content := range cSources35779 {
160		csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i))
161		csrcs = append(csrcs, csrcFile)
162		if err := os.WriteFile(csrcFile, []byte(content), 0444); err != nil {
163			t.Fatal(err)
164		}
165
166		obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i))
167		objs = append(objs, obj)
168		t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile)
169		if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil {
170			t.Logf("%s", out)
171			t.Fatal(err)
172		}
173	}
174
175	sysoObj := filepath.Join(dir, "ldr.syso")
176	t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs)
177	if out, err := testenv.Command(t, cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil {
178		t.Logf("%s", out)
179		t.Fatal(err)
180	}
181
182	cruft := [][]string{objs, csrcs}
183	for _, sl := range cruft {
184		for _, s := range sl {
185			if err := os.Remove(s); err != nil {
186				t.Fatal(err)
187			}
188		}
189	}
190
191	goFile := filepath.Join(dir, "main.go")
192	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
193		t.Fatal(err)
194	}
195
196	t.Logf("%s build", goTool)
197	cmd := testenv.Command(t, goTool, "build")
198	cmd.Dir = dir
199	cmd.Env = env
200	if out, err := cmd.CombinedOutput(); err != nil {
201		t.Logf("%s", out)
202		t.Fatal(err)
203	}
204}
205
206func TestGNUBuildIDDerivedFromGoBuildID(t *testing.T) {
207	testenv.MustHaveGoBuild(t)
208
209	t.Parallel()
210
211	goFile := filepath.Join(t.TempDir(), "notes.go")
212	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
213		t.Fatal(err)
214	}
215	outFile := filepath.Join(t.TempDir(), "notes.exe")
216	goTool := testenv.GoToolPath(t)
217
218	cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags", "-buildid 0x1234 -B gobuildid", goFile)
219	cmd.Dir = t.TempDir()
220
221	out, err := cmd.CombinedOutput()
222	if err != nil {
223		t.Logf("%s", out)
224		t.Fatal(err)
225	}
226
227	expectedGoBuildID := notsha256.Sum256([]byte("0x1234"))
228
229	gnuBuildID, err := buildid.ReadELFNote(outFile, string(ld.ELF_NOTE_BUILDINFO_NAME), ld.ELF_NOTE_BUILDINFO_TAG)
230	if err != nil || gnuBuildID == nil {
231		t.Fatalf("can't read GNU build ID")
232	}
233
234	if !bytes.Equal(gnuBuildID, expectedGoBuildID[:20]) {
235		t.Fatalf("build id not matching")
236	}
237}
238
239func TestMergeNoteSections(t *testing.T) {
240	testenv.MustHaveGoBuild(t)
241	expected := 1
242
243	switch runtime.GOOS {
244	case "linux", "dragonfly":
245	case "openbsd", "netbsd", "freebsd":
246		// These OSes require independent segment
247		expected = 2
248	default:
249		t.Skip("We should only test on elf output.")
250	}
251	t.Parallel()
252
253	goFile := filepath.Join(t.TempDir(), "notes.go")
254	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
255		t.Fatal(err)
256	}
257	outFile := filepath.Join(t.TempDir(), "notes.exe")
258	goTool := testenv.GoToolPath(t)
259	// sha1sum of "gopher"
260	id := "0xf4e8cd51ce8bae2996dc3b74639cdeaa1f7fee5f"
261	cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags",
262		"-B "+id, goFile)
263	cmd.Dir = t.TempDir()
264	if out, err := cmd.CombinedOutput(); err != nil {
265		t.Logf("%s", out)
266		t.Fatal(err)
267	}
268
269	ef, err := elf.Open(outFile)
270	if err != nil {
271		t.Fatalf("open elf file failed:%v", err)
272	}
273	defer ef.Close()
274	sec := ef.Section(".note.gnu.build-id")
275	if sec == nil {
276		t.Fatalf("can't find gnu build id")
277	}
278
279	sec = ef.Section(".note.go.buildid")
280	if sec == nil {
281		t.Fatalf("can't find go build id")
282	}
283	cnt := 0
284	for _, ph := range ef.Progs {
285		if ph.Type == elf.PT_NOTE {
286			cnt += 1
287		}
288	}
289	if cnt != expected {
290		t.Fatalf("want %d PT_NOTE segment, got %d", expected, cnt)
291	}
292}
293
294const pieSourceTemplate = `
295package main
296
297import "fmt"
298
299// Force the creation of a lot of type descriptors that will go into
300// the .data.rel.ro section.
301{{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{}
302{{end}}
303
304func main() {
305{{range $index, $element := .}}	fmt.Println(V{{$index}})
306{{end}}
307}
308`
309
310func TestPIESize(t *testing.T) {
311	testenv.MustHaveGoBuild(t)
312
313	// We don't want to test -linkmode=external if cgo is not supported.
314	// On some systems -buildmode=pie implies -linkmode=external, so just
315	// always skip the test if cgo is not supported.
316	testenv.MustHaveCGO(t)
317
318	if !platform.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) {
319		t.Skip("-buildmode=pie not supported")
320	}
321
322	t.Parallel()
323
324	tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate))
325
326	writeGo := func(t *testing.T, dir string) {
327		f, err := os.Create(filepath.Join(dir, "pie.go"))
328		if err != nil {
329			t.Fatal(err)
330		}
331
332		// Passing a 100-element slice here will cause
333		// pieSourceTemplate to create 100 variables with
334		// different types.
335		if err := tmpl.Execute(f, make([]byte, 100)); err != nil {
336			t.Fatal(err)
337		}
338
339		if err := f.Close(); err != nil {
340			t.Fatal(err)
341		}
342	}
343
344	for _, external := range []bool{false, true} {
345		external := external
346
347		name := "TestPieSize-"
348		if external {
349			name += "external"
350		} else {
351			name += "internal"
352		}
353		t.Run(name, func(t *testing.T) {
354			t.Parallel()
355
356			dir := t.TempDir()
357
358			writeGo(t, dir)
359
360			binexe := filepath.Join(dir, "exe")
361			binpie := filepath.Join(dir, "pie")
362			if external {
363				binexe += "external"
364				binpie += "external"
365			}
366
367			build := func(bin, mode string) error {
368				cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode)
369				if external {
370					cmd.Args = append(cmd.Args, "-ldflags=-linkmode=external")
371				}
372				cmd.Args = append(cmd.Args, "pie.go")
373				cmd.Dir = dir
374				t.Logf("%v", cmd.Args)
375				out, err := cmd.CombinedOutput()
376				if len(out) > 0 {
377					t.Logf("%s", out)
378				}
379				if err != nil {
380					t.Log(err)
381				}
382				return err
383			}
384
385			var errexe, errpie error
386			var wg sync.WaitGroup
387			wg.Add(2)
388			go func() {
389				defer wg.Done()
390				errexe = build(binexe, "exe")
391			}()
392			go func() {
393				defer wg.Done()
394				errpie = build(binpie, "pie")
395			}()
396			wg.Wait()
397			if errexe != nil || errpie != nil {
398				if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
399					testenv.SkipFlaky(t, 58806)
400				}
401				t.Fatal("link failed")
402			}
403
404			var sizeexe, sizepie uint64
405			if fi, err := os.Stat(binexe); err != nil {
406				t.Fatal(err)
407			} else {
408				sizeexe = uint64(fi.Size())
409			}
410			if fi, err := os.Stat(binpie); err != nil {
411				t.Fatal(err)
412			} else {
413				sizepie = uint64(fi.Size())
414			}
415
416			elfexe, err := elf.Open(binexe)
417			if err != nil {
418				t.Fatal(err)
419			}
420			defer elfexe.Close()
421
422			elfpie, err := elf.Open(binpie)
423			if err != nil {
424				t.Fatal(err)
425			}
426			defer elfpie.Close()
427
428			// The difference in size between exe and PIE
429			// should be approximately the difference in
430			// size of the .text section plus the size of
431			// the PIE dynamic data sections plus the
432			// difference in size of the .got and .plt
433			// sections if they exist.
434			// We ignore unallocated sections.
435			// There may be gaps between non-writeable and
436			// writable PT_LOAD segments. We also skip those
437			// gaps (see issue #36023).
438
439			textsize := func(ef *elf.File, name string) uint64 {
440				for _, s := range ef.Sections {
441					if s.Name == ".text" {
442						return s.Size
443					}
444				}
445				t.Fatalf("%s: no .text section", name)
446				return 0
447			}
448			textexe := textsize(elfexe, binexe)
449			textpie := textsize(elfpie, binpie)
450
451			dynsize := func(ef *elf.File) uint64 {
452				var ret uint64
453				for _, s := range ef.Sections {
454					if s.Flags&elf.SHF_ALLOC == 0 {
455						continue
456					}
457					switch s.Type {
458					case elf.SHT_DYNSYM, elf.SHT_STRTAB, elf.SHT_REL, elf.SHT_RELA, elf.SHT_HASH, elf.SHT_GNU_HASH, elf.SHT_GNU_VERDEF, elf.SHT_GNU_VERNEED, elf.SHT_GNU_VERSYM:
459						ret += s.Size
460					}
461					if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) {
462						ret += s.Size
463					}
464				}
465				return ret
466			}
467
468			dynexe := dynsize(elfexe)
469			dynpie := dynsize(elfpie)
470
471			extrasize := func(ef *elf.File) uint64 {
472				var ret uint64
473				// skip unallocated sections
474				for _, s := range ef.Sections {
475					if s.Flags&elf.SHF_ALLOC == 0 {
476						ret += s.Size
477					}
478				}
479				// also skip gaps between PT_LOAD segments
480				var prev *elf.Prog
481				for _, seg := range ef.Progs {
482					if seg.Type != elf.PT_LOAD {
483						continue
484					}
485					if prev != nil {
486						ret += seg.Off - prev.Off - prev.Filesz
487					}
488					prev = seg
489				}
490				return ret
491			}
492
493			extraexe := extrasize(elfexe)
494			extrapie := extrasize(elfpie)
495
496			if sizepie < sizeexe || sizepie-extrapie < sizeexe-extraexe {
497				return
498			}
499			diffReal := (sizepie - extrapie) - (sizeexe - extraexe)
500			diffExpected := (textpie + dynpie) - (textexe + dynexe)
501
502			t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected)
503
504			if diffReal > (diffExpected + diffExpected/10) {
505				t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected)
506			}
507		})
508	}
509}
510
511func TestIssue51939(t *testing.T) {
512	testenv.MustHaveGoBuild(t)
513	t.Parallel()
514	td := t.TempDir()
515	goFile := filepath.Join(td, "issue51939.go")
516	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
517		t.Fatal(err)
518	}
519	outFile := filepath.Join(td, "issue51939.exe")
520	goTool := testenv.GoToolPath(t)
521	cmd := testenv.Command(t, goTool, "build", "-o", outFile, goFile)
522	if out, err := cmd.CombinedOutput(); err != nil {
523		t.Logf("%s", out)
524		t.Fatal(err)
525	}
526
527	ef, err := elf.Open(outFile)
528	if err != nil {
529		t.Fatal(err)
530	}
531
532	for _, s := range ef.Sections {
533		if s.Flags&elf.SHF_ALLOC == 0 && s.Addr != 0 {
534			t.Errorf("section %s should not allocated with addr %x", s.Name, s.Addr)
535		}
536	}
537}
538
539func TestFlagR(t *testing.T) {
540	// Test that using the -R flag to specify a (large) alignment generates
541	// a working binary.
542	// (Test only on ELF for now. The alignment allowed differs from platform
543	// to platform.)
544	testenv.MustHaveGoBuild(t)
545	t.Parallel()
546	tmpdir := t.TempDir()
547	src := filepath.Join(tmpdir, "x.go")
548	if err := os.WriteFile(src, []byte(goSource), 0444); err != nil {
549		t.Fatal(err)
550	}
551	exe := filepath.Join(tmpdir, "x.exe")
552
553	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-R=0x100000", "-o", exe, src)
554	if out, err := cmd.CombinedOutput(); err != nil {
555		t.Fatalf("build failed: %v, output:\n%s", err, out)
556	}
557
558	cmd = testenv.Command(t, exe)
559	if out, err := cmd.CombinedOutput(); err != nil {
560		t.Errorf("executable failed to run: %v\n%s", err, out)
561	}
562}
563