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

View as plain text