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

View as plain text