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  		"runtime.bss":       "B",
   120  		"runtime.noptrbss":  "B",
   121  	}
   122  
   123  	out, err = testenv.Command(t, testenv.Executable(t), exe).CombinedOutput()
   124  	if err != nil {
   125  		t.Fatalf("go tool nm: %v\n%s", err, string(out))
   126  	}
   127  
   128  	relocated := func(code string) bool {
   129  		if runtime.GOOS == "aix" {
   130  			// On AIX, .data and .bss addresses are changed by the loader.
   131  			// Therefore, the values returned by the exec aren't the same
   132  			// than the ones inside the symbol table.
   133  			// In case of cgo, .text symbols are also changed.
   134  			switch code {
   135  			case "T", "t", "R", "r":
   136  				return iscgo
   137  			case "D", "d", "B", "b":
   138  				return true
   139  			}
   140  		}
   141  		if platform.DefaultPIE(runtime.GOOS, runtime.GOARCH, false) {
   142  			// Code is always relocated if the default buildmode is PIE.
   143  			return true
   144  		}
   145  		return false
   146  	}
   147  
   148  	dups := make(map[string]bool)
   149  	for _, line := range strings.Split(string(out), "\n") {
   150  		f := strings.Fields(line)
   151  		if len(f) < 3 {
   152  			continue
   153  		}
   154  		name := f[2]
   155  		if addr, found := names[name]; found {
   156  			if want, have := addr, "0x"+f[0]; have != want {
   157  				if !relocated(f[1]) {
   158  					t.Errorf("want %s address for %s symbol, but have %s", want, name, have)
   159  				}
   160  			}
   161  			delete(names, name)
   162  		}
   163  		if _, found := dups[name]; found {
   164  			t.Errorf("duplicate name of %q is found", name)
   165  		}
   166  		if stype, found := runtimeSyms[name]; found {
   167  			if runtime.GOOS == "plan9" && stype == "R" {
   168  				// no read-only data segment symbol on Plan 9
   169  				stype = "D"
   170  			}
   171  			if want, have := stype, strings.ToUpper(f[1]); have != want {
   172  				if runtime.GOOS == "android" && name == "runtime.epclntab" && have == "D" {
   173  					// TODO(#58807): Figure out why this fails and fix up the test.
   174  					t.Logf("(ignoring on %s) want %s type for %s symbol, but have %s", runtime.GOOS, want, name, have)
   175  				} else {
   176  					t.Errorf("want %s type for %s symbol, but have %s", want, name, have)
   177  				}
   178  			}
   179  			delete(runtimeSyms, name)
   180  		}
   181  	}
   182  	if len(names) > 0 {
   183  		t.Errorf("executable is missing %v symbols", names)
   184  	}
   185  	if len(runtimeSyms) > 0 {
   186  		t.Errorf("executable is missing %v symbols", runtimeSyms)
   187  	}
   188  }
   189  
   190  func TestGoExec(t *testing.T) {
   191  	testGoExec(t, false, false)
   192  }
   193  
   194  func testGoLib(t *testing.T, iscgo bool) {
   195  	t.Parallel()
   196  	tmpdir := t.TempDir()
   197  
   198  	gopath := filepath.Join(tmpdir, "gopath")
   199  	libpath := filepath.Join(gopath, "src", "mylib")
   200  
   201  	err := os.MkdirAll(libpath, 0777)
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	src := filepath.Join(libpath, "a.go")
   206  	file, err := os.Create(src)
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	err = template.Must(template.New("mylib").Parse(testlib)).Execute(file, iscgo)
   211  	if e := file.Close(); err == nil {
   212  		err = e
   213  	}
   214  	if err == nil {
   215  		err = os.WriteFile(filepath.Join(libpath, "go.mod"), []byte("module mylib\n"), 0666)
   216  	}
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=archive", "-o", "mylib.a", ".")
   222  	cmd.Dir = libpath
   223  	cmd.Env = append(os.Environ(), "GOPATH="+gopath)
   224  	out, err := cmd.CombinedOutput()
   225  	if err != nil {
   226  		t.Fatalf("building test lib failed: %s %s", err, out)
   227  	}
   228  	mylib := filepath.Join(libpath, "mylib.a")
   229  
   230  	out, err = testenv.Command(t, testenv.Executable(t), mylib).CombinedOutput()
   231  	if err != nil {
   232  		t.Fatalf("go tool nm: %v\n%s", err, string(out))
   233  	}
   234  	type symType struct {
   235  		Type  string
   236  		Name  string
   237  		CSym  bool
   238  		Found bool
   239  	}
   240  	var syms = []symType{
   241  		{"B", "mylib.Testdata", false, false},
   242  		{"T", "mylib.Testfunc", false, false},
   243  	}
   244  	if iscgo {
   245  		syms = append(syms, symType{"B", "mylib.TestCgodata", false, false})
   246  		syms = append(syms, symType{"T", "mylib.TestCgofunc", false, false})
   247  		if runtime.GOOS == "darwin" || runtime.GOOS == "ios" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") {
   248  			syms = append(syms, symType{"D", "_cgodata", true, false})
   249  			syms = append(syms, symType{"T", "_cgofunc", true, false})
   250  		} else if runtime.GOOS == "aix" {
   251  			syms = append(syms, symType{"D", "cgodata", true, false})
   252  			syms = append(syms, symType{"T", ".cgofunc", true, false})
   253  		} else {
   254  			syms = append(syms, symType{"D", "cgodata", true, false})
   255  			syms = append(syms, symType{"T", "cgofunc", true, false})
   256  		}
   257  	}
   258  
   259  	for _, line := range strings.Split(string(out), "\n") {
   260  		f := strings.Fields(line)
   261  		var typ, name string
   262  		var csym bool
   263  		if iscgo {
   264  			if len(f) < 4 {
   265  				continue
   266  			}
   267  			csym = !strings.Contains(f[0], "_go_.o")
   268  			typ = f[2]
   269  			name = f[3]
   270  		} else {
   271  			if len(f) < 3 {
   272  				continue
   273  			}
   274  			typ = f[1]
   275  			name = f[2]
   276  		}
   277  		for i := range syms {
   278  			sym := &syms[i]
   279  			if sym.Type == typ && sym.Name == name && sym.CSym == csym {
   280  				if sym.Found {
   281  					t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name)
   282  				}
   283  				sym.Found = true
   284  			}
   285  		}
   286  	}
   287  	for _, sym := range syms {
   288  		if !sym.Found {
   289  			t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name)
   290  		}
   291  	}
   292  }
   293  
   294  func TestGoLib(t *testing.T) {
   295  	testGoLib(t, false)
   296  }
   297  
   298  const testexec = `
   299  package main
   300  
   301  import "fmt"
   302  {{if .}}import "C"
   303  {{end}}
   304  
   305  func main() {
   306  	testfunc()
   307  }
   308  
   309  var testdata uint32
   310  
   311  func testfunc() {
   312  	fmt.Printf("main=%p\n", main)
   313  	fmt.Printf("testfunc=%p\n", testfunc)
   314  	fmt.Printf("testdata=%p\n", &testdata)
   315  }
   316  `
   317  
   318  const testlib = `
   319  package mylib
   320  
   321  {{if .}}
   322  // int cgodata = 5;
   323  // void cgofunc(void) {}
   324  import "C"
   325  
   326  var TestCgodata = C.cgodata
   327  
   328  func TestCgofunc() {
   329  	C.cgofunc()
   330  }
   331  {{end}}
   332  
   333  var Testdata uint32
   334  
   335  func Testfunc() {}
   336  `
   337  

View as plain text