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  	"fmt"
     9  	"internal/obscuretestdata"
    10  	"internal/testenv"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  	"text/template"
    18  )
    19  
    20  var testnmpath string // path to nm command created for testing purposes
    21  
    22  // The TestMain function creates a nm command for testing purposes and
    23  // deletes it after the tests have been run.
    24  func TestMain(m *testing.M) {
    25  	os.Exit(testMain(m))
    26  }
    27  
    28  func testMain(m *testing.M) int {
    29  	if !testenv.HasGoBuild() {
    30  		return 0
    31  	}
    32  
    33  	tmpDir, err := os.MkdirTemp("", "TestNM")
    34  	if err != nil {
    35  		fmt.Println("TempDir failed:", err)
    36  		return 2
    37  	}
    38  	defer os.RemoveAll(tmpDir)
    39  
    40  	testnmpath = filepath.Join(tmpDir, "testnm.exe")
    41  	gotool, err := testenv.GoTool()
    42  	if err != nil {
    43  		fmt.Println("GoTool failed:", err)
    44  		return 2
    45  	}
    46  	out, err := exec.Command(gotool, "build", "-o", testnmpath, "cmd/nm").CombinedOutput()
    47  	if err != nil {
    48  		fmt.Printf("go build -o %v cmd/nm: %v\n%s", testnmpath, err, string(out))
    49  		return 2
    50  	}
    51  
    52  	return m.Run()
    53  }
    54  
    55  func TestNonGoExecs(t *testing.T) {
    56  	t.Parallel()
    57  	testfiles := []string{
    58  		"debug/elf/testdata/gcc-386-freebsd-exec",
    59  		"debug/elf/testdata/gcc-amd64-linux-exec",
    60  		"debug/macho/testdata/gcc-386-darwin-exec.base64",   // golang.org/issue/34986
    61  		"debug/macho/testdata/gcc-amd64-darwin-exec.base64", // golang.org/issue/34986
    62  		// "debug/pe/testdata/gcc-amd64-mingw-exec", // no symbols!
    63  		"debug/pe/testdata/gcc-386-mingw-exec",
    64  		"debug/plan9obj/testdata/amd64-plan9-exec",
    65  		"debug/plan9obj/testdata/386-plan9-exec",
    66  		"internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec",
    67  	}
    68  	for _, f := range testfiles {
    69  		exepath := filepath.Join(runtime.GOROOT(), "src", f)
    70  		if strings.HasSuffix(f, ".base64") {
    71  			tf, err := obscuretestdata.DecodeToTempFile(exepath)
    72  			if err != nil {
    73  				t.Errorf("obscuretestdata.DecodeToTempFile(%s): %v", exepath, err)
    74  				continue
    75  			}
    76  			defer os.Remove(tf)
    77  			exepath = tf
    78  		}
    79  
    80  		cmd := exec.Command(testnmpath, exepath)
    81  		out, err := cmd.CombinedOutput()
    82  		if err != nil {
    83  			t.Errorf("go tool nm %v: %v\n%s", exepath, err, string(out))
    84  		}
    85  	}
    86  }
    87  
    88  func testGoExec(t *testing.T, iscgo, isexternallinker bool) {
    89  	t.Parallel()
    90  	tmpdir, err := os.MkdirTemp("", "TestGoExec")
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	defer os.RemoveAll(tmpdir)
    95  
    96  	src := filepath.Join(tmpdir, "a.go")
    97  	file, err := os.Create(src)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	err = template.Must(template.New("main").Parse(testexec)).Execute(file, iscgo)
   102  	if e := file.Close(); err == nil {
   103  		err = e
   104  	}
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  
   109  	exe := filepath.Join(tmpdir, "a.exe")
   110  	args := []string{"build", "-o", exe}
   111  	if iscgo {
   112  		linkmode := "internal"
   113  		if isexternallinker {
   114  			linkmode = "external"
   115  		}
   116  		args = append(args, "-ldflags", "-linkmode="+linkmode)
   117  	}
   118  	args = append(args, src)
   119  	out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
   120  	if err != nil {
   121  		t.Fatalf("building test executable failed: %s %s", err, out)
   122  	}
   123  
   124  	out, err = exec.Command(exe).CombinedOutput()
   125  	if err != nil {
   126  		t.Fatalf("running test executable failed: %s %s", err, out)
   127  	}
   128  	names := make(map[string]string)
   129  	for _, line := range strings.Split(string(out), "\n") {
   130  		if line == "" {
   131  			continue
   132  		}
   133  		f := strings.Split(line, "=")
   134  		if len(f) != 2 {
   135  			t.Fatalf("unexpected output line: %q", line)
   136  		}
   137  		names["main."+f[0]] = f[1]
   138  	}
   139  
   140  	runtimeSyms := map[string]string{
   141  		"runtime.text":      "T",
   142  		"runtime.etext":     "T",
   143  		"runtime.rodata":    "R",
   144  		"runtime.erodata":   "R",
   145  		"runtime.epclntab":  "R",
   146  		"runtime.noptrdata": "D",
   147  	}
   148  
   149  	if runtime.GOOS == "aix" && iscgo {
   150  		// pclntab is moved to .data section on AIX.
   151  		runtimeSyms["runtime.epclntab"] = "D"
   152  	}
   153  
   154  	out, err = exec.Command(testnmpath, exe).CombinedOutput()
   155  	if err != nil {
   156  		t.Fatalf("go tool nm: %v\n%s", err, string(out))
   157  	}
   158  
   159  	relocated := func(code string) bool {
   160  		if runtime.GOOS == "aix" {
   161  			// On AIX, .data and .bss addresses are changed by the loader.
   162  			// Therefore, the values returned by the exec aren't the same
   163  			// than the ones inside the symbol table.
   164  			// In case of cgo, .text symbols are also changed.
   165  			switch code {
   166  			case "T", "t", "R", "r":
   167  				return iscgo
   168  			case "D", "d", "B", "b":
   169  				return true
   170  			}
   171  		}
   172  		if runtime.GOOS == "windows" {
   173  			return true
   174  		}
   175  		if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
   176  			return true // On darwin/arm64 everything is PIE
   177  		}
   178  		return false
   179  	}
   180  
   181  	dups := make(map[string]bool)
   182  	for _, line := range strings.Split(string(out), "\n") {
   183  		f := strings.Fields(line)
   184  		if len(f) < 3 {
   185  			continue
   186  		}
   187  		name := f[2]
   188  		if addr, found := names[name]; found {
   189  			if want, have := addr, "0x"+f[0]; have != want {
   190  				if !relocated(f[1]) {
   191  					t.Errorf("want %s address for %s symbol, but have %s", want, name, have)
   192  				}
   193  			}
   194  			delete(names, name)
   195  		}
   196  		if _, found := dups[name]; found {
   197  			t.Errorf("duplicate name of %q is found", name)
   198  		}
   199  		if stype, found := runtimeSyms[name]; found {
   200  			if runtime.GOOS == "plan9" && stype == "R" {
   201  				// no read-only data segment symbol on Plan 9
   202  				stype = "D"
   203  			}
   204  			if want, have := stype, strings.ToUpper(f[1]); have != want {
   205  				t.Errorf("want %s type for %s symbol, but have %s", want, name, have)
   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  	args := []string{"install", "mylib"}
   254  	cmd := exec.Command(testenv.GoToolPath(t), args...)
   255  	cmd.Dir = libpath
   256  	cmd.Env = append(os.Environ(), "GOPATH="+gopath)
   257  	out, err := cmd.CombinedOutput()
   258  	if err != nil {
   259  		t.Fatalf("building test lib failed: %s %s", err, out)
   260  	}
   261  	pat := filepath.Join(gopath, "pkg", "*", "mylib.a")
   262  	ms, err := filepath.Glob(pat)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	if len(ms) == 0 {
   267  		t.Fatalf("cannot found paths for pattern %s", pat)
   268  	}
   269  	mylib := ms[0]
   270  
   271  	out, err = exec.Command(testnmpath, mylib).CombinedOutput()
   272  	if err != nil {
   273  		t.Fatalf("go tool nm: %v\n%s", err, string(out))
   274  	}
   275  	type symType struct {
   276  		Type  string
   277  		Name  string
   278  		CSym  bool
   279  		Found bool
   280  	}
   281  	var syms = []symType{
   282  		{"B", "mylib.Testdata", false, false},
   283  		{"T", "mylib.Testfunc", false, false},
   284  	}
   285  	if iscgo {
   286  		syms = append(syms, symType{"B", "mylib.TestCgodata", false, false})
   287  		syms = append(syms, symType{"T", "mylib.TestCgofunc", false, false})
   288  		if runtime.GOOS == "darwin" || runtime.GOOS == "ios" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") {
   289  			syms = append(syms, symType{"D", "_cgodata", true, false})
   290  			syms = append(syms, symType{"T", "_cgofunc", true, false})
   291  		} else if runtime.GOOS == "aix" {
   292  			syms = append(syms, symType{"D", "cgodata", true, false})
   293  			syms = append(syms, symType{"T", ".cgofunc", true, false})
   294  		} else {
   295  			syms = append(syms, symType{"D", "cgodata", true, false})
   296  			syms = append(syms, symType{"T", "cgofunc", true, false})
   297  		}
   298  	}
   299  
   300  	for _, line := range strings.Split(string(out), "\n") {
   301  		f := strings.Fields(line)
   302  		var typ, name string
   303  		var csym bool
   304  		if iscgo {
   305  			if len(f) < 4 {
   306  				continue
   307  			}
   308  			csym = !strings.Contains(f[0], "_go_.o")
   309  			typ = f[2]
   310  			name = f[3]
   311  		} else {
   312  			if len(f) < 3 {
   313  				continue
   314  			}
   315  			typ = f[1]
   316  			name = f[2]
   317  		}
   318  		for i := range syms {
   319  			sym := &syms[i]
   320  			if sym.Type == typ && sym.Name == name && sym.CSym == csym {
   321  				if sym.Found {
   322  					t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name)
   323  				}
   324  				sym.Found = true
   325  			}
   326  		}
   327  	}
   328  	for _, sym := range syms {
   329  		if !sym.Found {
   330  			t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name)
   331  		}
   332  	}
   333  }
   334  
   335  func TestGoLib(t *testing.T) {
   336  	testGoLib(t, false)
   337  }
   338  
   339  const testexec = `
   340  package main
   341  
   342  import "fmt"
   343  {{if .}}import "C"
   344  {{end}}
   345  
   346  func main() {
   347  	testfunc()
   348  }
   349  
   350  var testdata uint32
   351  
   352  func testfunc() {
   353  	fmt.Printf("main=%p\n", main)
   354  	fmt.Printf("testfunc=%p\n", testfunc)
   355  	fmt.Printf("testdata=%p\n", &testdata)
   356  }
   357  `
   358  
   359  const testlib = `
   360  package mylib
   361  
   362  {{if .}}
   363  // int cgodata = 5;
   364  // void cgofunc(void) {}
   365  import "C"
   366  
   367  var TestCgodata = C.cgodata
   368  
   369  func TestCgofunc() {
   370  	C.cgofunc()
   371  }
   372  {{end}}
   373  
   374  var Testdata uint32
   375  
   376  func Testfunc() {}
   377  `
   378  

View as plain text