Source file src/cmd/nm/nm_test.go

     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  
     5  package main
     6  
     7  import (
     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.
    22  func 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.
    33  func 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  
    46  var (
    47  	nmPathOnce sync.Once
    48  	nmExePath  string
    49  	nmPathErr  error
    50  )
    51  
    52  func 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  
    85  func 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  
   218  func TestGoExec(t *testing.T) {
   219  	testGoExec(t, false, false)
   220  }
   221  
   222  func 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  
   326  func TestGoLib(t *testing.T) {
   327  	testGoLib(t, false)
   328  }
   329  
   330  const testexec = `
   331  package main
   332  
   333  import "fmt"
   334  {{if .}}import "C"
   335  {{end}}
   336  
   337  func main() {
   338  	testfunc()
   339  }
   340  
   341  var testdata uint32
   342  
   343  func 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  
   350  const testlib = `
   351  package mylib
   352  
   353  {{if .}}
   354  // int cgodata = 5;
   355  // void cgofunc(void) {}
   356  import "C"
   357  
   358  var TestCgodata = C.cgodata
   359  
   360  func TestCgofunc() {
   361  	C.cgofunc()
   362  }
   363  {{end}}
   364  
   365  var Testdata uint32
   366  
   367  func Testfunc() {}
   368  `
   369  

View as plain text