github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/nm/nm_test.go (about)

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