// 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() {} `