1// Copyright 2014 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 main
6
7import (
8	"internal/obscuretestdata"
9	"internal/platform"
10	"internal/testenv"
11	"os"
12	"path/filepath"
13	"runtime"
14	"strings"
15	"sync"
16	"testing"
17	"text/template"
18)
19
20// TestMain executes the test binary as the nm command if
21// GO_NMTEST_IS_NM is set, and runs the tests otherwise.
22func TestMain(m *testing.M) {
23	if os.Getenv("GO_NMTEST_IS_NM") != "" {
24		main()
25		os.Exit(0)
26	}
27
28	os.Setenv("GO_NMTEST_IS_NM", "1") // Set for subprocesses to inherit.
29	os.Exit(m.Run())
30}
31
32// nmPath returns the path to the "nm" binary to run.
33func nmPath(t testing.TB) string {
34	t.Helper()
35	testenv.MustHaveExec(t)
36
37	nmPathOnce.Do(func() {
38		nmExePath, nmPathErr = os.Executable()
39	})
40	if nmPathErr != nil {
41		t.Fatal(nmPathErr)
42	}
43	return nmExePath
44}
45
46var (
47	nmPathOnce sync.Once
48	nmExePath  string
49	nmPathErr  error
50)
51
52func TestNonGoExecs(t *testing.T) {
53	t.Parallel()
54	testfiles := []string{
55		"debug/elf/testdata/gcc-386-freebsd-exec",
56		"debug/elf/testdata/gcc-amd64-linux-exec",
57		"debug/macho/testdata/gcc-386-darwin-exec.base64",   // golang.org/issue/34986
58		"debug/macho/testdata/gcc-amd64-darwin-exec.base64", // golang.org/issue/34986
59		// "debug/pe/testdata/gcc-amd64-mingw-exec", // no symbols!
60		"debug/pe/testdata/gcc-386-mingw-exec",
61		"debug/plan9obj/testdata/amd64-plan9-exec",
62		"debug/plan9obj/testdata/386-plan9-exec",
63		"internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec",
64	}
65	for _, f := range testfiles {
66		exepath := filepath.Join(testenv.GOROOT(t), "src", f)
67		if strings.HasSuffix(f, ".base64") {
68			tf, err := obscuretestdata.DecodeToTempFile(exepath)
69			if err != nil {
70				t.Errorf("obscuretestdata.DecodeToTempFile(%s): %v", exepath, err)
71				continue
72			}
73			defer os.Remove(tf)
74			exepath = tf
75		}
76
77		cmd := testenv.Command(t, nmPath(t), exepath)
78		out, err := cmd.CombinedOutput()
79		if err != nil {
80			t.Errorf("go tool nm %v: %v\n%s", exepath, err, string(out))
81		}
82	}
83}
84
85func testGoExec(t *testing.T, iscgo, isexternallinker bool) {
86	t.Parallel()
87	tmpdir, err := os.MkdirTemp("", "TestGoExec")
88	if err != nil {
89		t.Fatal(err)
90	}
91	defer os.RemoveAll(tmpdir)
92
93	src := filepath.Join(tmpdir, "a.go")
94	file, err := os.Create(src)
95	if err != nil {
96		t.Fatal(err)
97	}
98	err = template.Must(template.New("main").Parse(testexec)).Execute(file, iscgo)
99	if e := file.Close(); err == nil {
100		err = e
101	}
102	if err != nil {
103		t.Fatal(err)
104	}
105
106	exe := filepath.Join(tmpdir, "a.exe")
107	args := []string{"build", "-o", exe}
108	if iscgo {
109		linkmode := "internal"
110		if isexternallinker {
111			linkmode = "external"
112		}
113		args = append(args, "-ldflags", "-linkmode="+linkmode)
114	}
115	args = append(args, src)
116	out, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput()
117	if err != nil {
118		t.Fatalf("building test executable failed: %s %s", err, out)
119	}
120
121	out, err = testenv.Command(t, exe).CombinedOutput()
122	if err != nil {
123		t.Fatalf("running test executable failed: %s %s", err, out)
124	}
125	names := make(map[string]string)
126	for _, line := range strings.Split(string(out), "\n") {
127		if line == "" {
128			continue
129		}
130		f := strings.Split(line, "=")
131		if len(f) != 2 {
132			t.Fatalf("unexpected output line: %q", line)
133		}
134		names["main."+f[0]] = f[1]
135	}
136
137	runtimeSyms := map[string]string{
138		"runtime.text":      "T",
139		"runtime.etext":     "T",
140		"runtime.rodata":    "R",
141		"runtime.erodata":   "R",
142		"runtime.epclntab":  "R",
143		"runtime.noptrdata": "D",
144	}
145
146	if runtime.GOOS == "aix" && iscgo {
147		// pclntab is moved to .data section on AIX.
148		runtimeSyms["runtime.epclntab"] = "D"
149	}
150
151	out, err = testenv.Command(t, nmPath(t), exe).CombinedOutput()
152	if err != nil {
153		t.Fatalf("go tool nm: %v\n%s", err, string(out))
154	}
155
156	relocated := func(code string) bool {
157		if runtime.GOOS == "aix" {
158			// On AIX, .data and .bss addresses are changed by the loader.
159			// Therefore, the values returned by the exec aren't the same
160			// than the ones inside the symbol table.
161			// In case of cgo, .text symbols are also changed.
162			switch code {
163			case "T", "t", "R", "r":
164				return iscgo
165			case "D", "d", "B", "b":
166				return true
167			}
168		}
169		if platform.DefaultPIE(runtime.GOOS, runtime.GOARCH, false) {
170			// Code is always relocated if the default buildmode is PIE.
171			return true
172		}
173		return false
174	}
175
176	dups := make(map[string]bool)
177	for _, line := range strings.Split(string(out), "\n") {
178		f := strings.Fields(line)
179		if len(f) < 3 {
180			continue
181		}
182		name := f[2]
183		if addr, found := names[name]; found {
184			if want, have := addr, "0x"+f[0]; have != want {
185				if !relocated(f[1]) {
186					t.Errorf("want %s address for %s symbol, but have %s", want, name, have)
187				}
188			}
189			delete(names, name)
190		}
191		if _, found := dups[name]; found {
192			t.Errorf("duplicate name of %q is found", name)
193		}
194		if stype, found := runtimeSyms[name]; found {
195			if runtime.GOOS == "plan9" && stype == "R" {
196				// no read-only data segment symbol on Plan 9
197				stype = "D"
198			}
199			if want, have := stype, strings.ToUpper(f[1]); have != want {
200				if runtime.GOOS == "android" && name == "runtime.epclntab" && have == "D" {
201					// TODO(#58807): Figure out why this fails and fix up the test.
202					t.Logf("(ignoring on %s) want %s type for %s symbol, but have %s", runtime.GOOS, want, name, have)
203				} else {
204					t.Errorf("want %s type for %s symbol, but have %s", want, name, have)
205				}
206			}
207			delete(runtimeSyms, name)
208		}
209	}
210	if len(names) > 0 {
211		t.Errorf("executable is missing %v symbols", names)
212	}
213	if len(runtimeSyms) > 0 {
214		t.Errorf("executable is missing %v symbols", runtimeSyms)
215	}
216}
217
218func TestGoExec(t *testing.T) {
219	testGoExec(t, false, false)
220}
221
222func testGoLib(t *testing.T, iscgo bool) {
223	t.Parallel()
224	tmpdir, err := os.MkdirTemp("", "TestGoLib")
225	if err != nil {
226		t.Fatal(err)
227	}
228	defer os.RemoveAll(tmpdir)
229
230	gopath := filepath.Join(tmpdir, "gopath")
231	libpath := filepath.Join(gopath, "src", "mylib")
232
233	err = os.MkdirAll(libpath, 0777)
234	if err != nil {
235		t.Fatal(err)
236	}
237	src := filepath.Join(libpath, "a.go")
238	file, err := os.Create(src)
239	if err != nil {
240		t.Fatal(err)
241	}
242	err = template.Must(template.New("mylib").Parse(testlib)).Execute(file, iscgo)
243	if e := file.Close(); err == nil {
244		err = e
245	}
246	if err == nil {
247		err = os.WriteFile(filepath.Join(libpath, "go.mod"), []byte("module mylib\n"), 0666)
248	}
249	if err != nil {
250		t.Fatal(err)
251	}
252
253	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=archive", "-o", "mylib.a", ".")
254	cmd.Dir = libpath
255	cmd.Env = append(os.Environ(), "GOPATH="+gopath)
256	out, err := cmd.CombinedOutput()
257	if err != nil {
258		t.Fatalf("building test lib failed: %s %s", err, out)
259	}
260	mylib := filepath.Join(libpath, "mylib.a")
261
262	out, err = testenv.Command(t, nmPath(t), mylib).CombinedOutput()
263	if err != nil {
264		t.Fatalf("go tool nm: %v\n%s", err, string(out))
265	}
266	type symType struct {
267		Type  string
268		Name  string
269		CSym  bool
270		Found bool
271	}
272	var syms = []symType{
273		{"B", "mylib.Testdata", false, false},
274		{"T", "mylib.Testfunc", false, false},
275	}
276	if iscgo {
277		syms = append(syms, symType{"B", "mylib.TestCgodata", false, false})
278		syms = append(syms, symType{"T", "mylib.TestCgofunc", false, false})
279		if runtime.GOOS == "darwin" || runtime.GOOS == "ios" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") {
280			syms = append(syms, symType{"D", "_cgodata", true, false})
281			syms = append(syms, symType{"T", "_cgofunc", true, false})
282		} else if runtime.GOOS == "aix" {
283			syms = append(syms, symType{"D", "cgodata", true, false})
284			syms = append(syms, symType{"T", ".cgofunc", true, false})
285		} else {
286			syms = append(syms, symType{"D", "cgodata", true, false})
287			syms = append(syms, symType{"T", "cgofunc", true, false})
288		}
289	}
290
291	for _, line := range strings.Split(string(out), "\n") {
292		f := strings.Fields(line)
293		var typ, name string
294		var csym bool
295		if iscgo {
296			if len(f) < 4 {
297				continue
298			}
299			csym = !strings.Contains(f[0], "_go_.o")
300			typ = f[2]
301			name = f[3]
302		} else {
303			if len(f) < 3 {
304				continue
305			}
306			typ = f[1]
307			name = f[2]
308		}
309		for i := range syms {
310			sym := &syms[i]
311			if sym.Type == typ && sym.Name == name && sym.CSym == csym {
312				if sym.Found {
313					t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name)
314				}
315				sym.Found = true
316			}
317		}
318	}
319	for _, sym := range syms {
320		if !sym.Found {
321			t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name)
322		}
323	}
324}
325
326func TestGoLib(t *testing.T) {
327	testGoLib(t, false)
328}
329
330const testexec = `
331package main
332
333import "fmt"
334{{if .}}import "C"
335{{end}}
336
337func main() {
338	testfunc()
339}
340
341var testdata uint32
342
343func testfunc() {
344	fmt.Printf("main=%p\n", main)
345	fmt.Printf("testfunc=%p\n", testfunc)
346	fmt.Printf("testdata=%p\n", &testdata)
347}
348`
349
350const testlib = `
351package mylib
352
353{{if .}}
354// int cgodata = 5;
355// void cgofunc(void) {}
356import "C"
357
358var TestCgodata = C.cgodata
359
360func TestCgofunc() {
361	C.cgofunc()
362}
363{{end}}
364
365var Testdata uint32
366
367func Testfunc() {}
368`
369