diff options
Diffstat (limited to 'src/cmd/nm/nm_test.go')
-rw-r--r-- | src/cmd/nm/nm_test.go | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/src/cmd/nm/nm_test.go b/src/cmd/nm/nm_test.go new file mode 100644 index 0000000..7d8358e --- /dev/null +++ b/src/cmd/nm/nm_test.go @@ -0,0 +1,364 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "internal/obscuretestdata" + "internal/testenv" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "testing" + "text/template" +) + +// TestMain executes the test binary as the nm command if +// GO_NMTEST_IS_NM is set, and runs the tests otherwise. +func TestMain(m *testing.M) { + if os.Getenv("GO_NMTEST_IS_NM") != "" { + main() + os.Exit(0) + } + + os.Setenv("GO_NMTEST_IS_NM", "1") // Set for subprocesses to inherit. + os.Exit(m.Run()) +} + +// nmPath returns the path to the "nm" binary to run. +func nmPath(t testing.TB) string { + t.Helper() + testenv.MustHaveExec(t) + + nmPathOnce.Do(func() { + nmExePath, nmPathErr = os.Executable() + }) + if nmPathErr != nil { + t.Fatal(nmPathErr) + } + return nmExePath +} + +var ( + nmPathOnce sync.Once + nmExePath string + nmPathErr error +) + +func TestNonGoExecs(t *testing.T) { + t.Parallel() + testfiles := []string{ + "debug/elf/testdata/gcc-386-freebsd-exec", + "debug/elf/testdata/gcc-amd64-linux-exec", + "debug/macho/testdata/gcc-386-darwin-exec.base64", // golang.org/issue/34986 + "debug/macho/testdata/gcc-amd64-darwin-exec.base64", // golang.org/issue/34986 + // "debug/pe/testdata/gcc-amd64-mingw-exec", // no symbols! + "debug/pe/testdata/gcc-386-mingw-exec", + "debug/plan9obj/testdata/amd64-plan9-exec", + "debug/plan9obj/testdata/386-plan9-exec", + "internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec", + } + for _, f := range testfiles { + exepath := filepath.Join(testenv.GOROOT(t), "src", f) + if strings.HasSuffix(f, ".base64") { + tf, err := obscuretestdata.DecodeToTempFile(exepath) + if err != nil { + t.Errorf("obscuretestdata.DecodeToTempFile(%s): %v", exepath, err) + continue + } + defer os.Remove(tf) + exepath = tf + } + + cmd := testenv.Command(t, nmPath(t), exepath) + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("go tool nm %v: %v\n%s", exepath, err, string(out)) + } + } +} + +func testGoExec(t *testing.T, iscgo, isexternallinker bool) { + t.Parallel() + tmpdir, err := os.MkdirTemp("", "TestGoExec") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + src := filepath.Join(tmpdir, "a.go") + file, err := os.Create(src) + if err != nil { + t.Fatal(err) + } + err = template.Must(template.New("main").Parse(testexec)).Execute(file, iscgo) + if e := file.Close(); err == nil { + err = e + } + if err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "a.exe") + args := []string{"build", "-o", exe} + if iscgo { + linkmode := "internal" + if isexternallinker { + linkmode = "external" + } + args = append(args, "-ldflags", "-linkmode="+linkmode) + } + args = append(args, src) + out, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput() + if err != nil { + t.Fatalf("building test executable failed: %s %s", err, out) + } + + out, err = testenv.Command(t, exe).CombinedOutput() + if err != nil { + t.Fatalf("running test executable failed: %s %s", err, out) + } + names := make(map[string]string) + for _, line := range strings.Split(string(out), "\n") { + if line == "" { + continue + } + f := strings.Split(line, "=") + if len(f) != 2 { + t.Fatalf("unexpected output line: %q", line) + } + names["main."+f[0]] = f[1] + } + + runtimeSyms := map[string]string{ + "runtime.text": "T", + "runtime.etext": "T", + "runtime.rodata": "R", + "runtime.erodata": "R", + "runtime.epclntab": "R", + "runtime.noptrdata": "D", + } + + if runtime.GOOS == "aix" && iscgo { + // pclntab is moved to .data section on AIX. + runtimeSyms["runtime.epclntab"] = "D" + } + + out, err = testenv.Command(t, nmPath(t), exe).CombinedOutput() + if err != nil { + t.Fatalf("go tool nm: %v\n%s", err, string(out)) + } + + relocated := func(code string) bool { + if runtime.GOOS == "aix" { + // On AIX, .data and .bss addresses are changed by the loader. + // Therefore, the values returned by the exec aren't the same + // than the ones inside the symbol table. + // In case of cgo, .text symbols are also changed. + switch code { + case "T", "t", "R", "r": + return iscgo + case "D", "d", "B", "b": + return true + } + } + if runtime.GOOS == "windows" { + return true + } + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { + return true // On darwin/arm64 everything is PIE + } + return false + } + + dups := make(map[string]bool) + for _, line := range strings.Split(string(out), "\n") { + f := strings.Fields(line) + if len(f) < 3 { + continue + } + name := f[2] + if addr, found := names[name]; found { + if want, have := addr, "0x"+f[0]; have != want { + if !relocated(f[1]) { + t.Errorf("want %s address for %s symbol, but have %s", want, name, have) + } + } + delete(names, name) + } + if _, found := dups[name]; found { + t.Errorf("duplicate name of %q is found", name) + } + if stype, found := runtimeSyms[name]; found { + if runtime.GOOS == "plan9" && stype == "R" { + // no read-only data segment symbol on Plan 9 + stype = "D" + } + if want, have := stype, strings.ToUpper(f[1]); have != want { + t.Errorf("want %s type for %s symbol, but have %s", want, name, have) + } + delete(runtimeSyms, name) + } + } + if len(names) > 0 { + t.Errorf("executable is missing %v symbols", names) + } + if len(runtimeSyms) > 0 { + t.Errorf("executable is missing %v symbols", runtimeSyms) + } +} + +func TestGoExec(t *testing.T) { + testGoExec(t, false, false) +} + +func testGoLib(t *testing.T, iscgo bool) { + t.Parallel() + tmpdir, err := os.MkdirTemp("", "TestGoLib") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + gopath := filepath.Join(tmpdir, "gopath") + libpath := filepath.Join(gopath, "src", "mylib") + + err = os.MkdirAll(libpath, 0777) + if err != nil { + t.Fatal(err) + } + src := filepath.Join(libpath, "a.go") + file, err := os.Create(src) + if err != nil { + t.Fatal(err) + } + err = template.Must(template.New("mylib").Parse(testlib)).Execute(file, iscgo) + if e := file.Close(); err == nil { + err = e + } + if err == nil { + err = os.WriteFile(filepath.Join(libpath, "go.mod"), []byte("module mylib\n"), 0666) + } + if err != nil { + t.Fatal(err) + } + + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=archive", "-o", "mylib.a", ".") + cmd.Dir = libpath + cmd.Env = append(os.Environ(), "GOPATH="+gopath) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("building test lib failed: %s %s", err, out) + } + mylib := filepath.Join(libpath, "mylib.a") + + out, err = testenv.Command(t, nmPath(t), mylib).CombinedOutput() + if err != nil { + t.Fatalf("go tool nm: %v\n%s", err, string(out)) + } + type symType struct { + Type string + Name string + CSym bool + Found bool + } + var syms = []symType{ + {"B", "mylib.Testdata", false, false}, + {"T", "mylib.Testfunc", false, false}, + } + if iscgo { + syms = append(syms, symType{"B", "mylib.TestCgodata", false, false}) + syms = append(syms, symType{"T", "mylib.TestCgofunc", false, false}) + if runtime.GOOS == "darwin" || runtime.GOOS == "ios" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") { + syms = append(syms, symType{"D", "_cgodata", true, false}) + syms = append(syms, symType{"T", "_cgofunc", true, false}) + } else if runtime.GOOS == "aix" { + syms = append(syms, symType{"D", "cgodata", true, false}) + syms = append(syms, symType{"T", ".cgofunc", true, false}) + } else { + syms = append(syms, symType{"D", "cgodata", true, false}) + syms = append(syms, symType{"T", "cgofunc", true, false}) + } + } + + for _, line := range strings.Split(string(out), "\n") { + f := strings.Fields(line) + var typ, name string + var csym bool + if iscgo { + if len(f) < 4 { + continue + } + csym = !strings.Contains(f[0], "_go_.o") + typ = f[2] + name = f[3] + } else { + if len(f) < 3 { + continue + } + typ = f[1] + name = f[2] + } + for i := range syms { + sym := &syms[i] + if sym.Type == typ && sym.Name == name && sym.CSym == csym { + if sym.Found { + t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name) + } + sym.Found = true + } + } + } + for _, sym := range syms { + if !sym.Found { + t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name) + } + } +} + +func TestGoLib(t *testing.T) { + testGoLib(t, false) +} + +const testexec = ` +package main + +import "fmt" +{{if .}}import "C" +{{end}} + +func main() { + testfunc() +} + +var testdata uint32 + +func testfunc() { + fmt.Printf("main=%p\n", main) + fmt.Printf("testfunc=%p\n", testfunc) + fmt.Printf("testdata=%p\n", &testdata) +} +` + +const testlib = ` +package mylib + +{{if .}} +// int cgodata = 5; +// void cgofunc(void) {} +import "C" + +var TestCgodata = C.cgodata + +func TestCgofunc() { + C.cgofunc() +} +{{end}} + +var Testdata uint32 + +func Testfunc() {} +` |