Source file src/cmd/objdump/objdump_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  	"cmd/internal/notsha256"
     9  	"flag"
    10  	"fmt"
    11  	"internal/platform"
    12  	"internal/testenv"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  )
    20  
    21  // TestMain executes the test binary as the objdump command if
    22  // GO_OBJDUMPTEST_IS_OBJDUMP is set, and runs the test otherwise.
    23  func TestMain(m *testing.M) {
    24  	if os.Getenv("GO_OBJDUMPTEST_IS_OBJDUMP") != "" {
    25  		main()
    26  		os.Exit(0)
    27  	}
    28  
    29  	os.Setenv("GO_OBJDUMPTEST_IS_OBJDUMP", "1")
    30  	os.Exit(m.Run())
    31  }
    32  
    33  // objdumpPath returns the path to the "objdump" binary to run.
    34  func objdumpPath(t testing.TB) string {
    35  	t.Helper()
    36  	testenv.MustHaveExec(t)
    37  
    38  	objdumpPathOnce.Do(func() {
    39  		objdumpExePath, objdumpPathErr = os.Executable()
    40  	})
    41  	if objdumpPathErr != nil {
    42  		t.Fatal(objdumpPathErr)
    43  	}
    44  	return objdumpExePath
    45  }
    46  
    47  var (
    48  	objdumpPathOnce sync.Once
    49  	objdumpExePath  string
    50  	objdumpPathErr  error
    51  )
    52  
    53  var x86Need = []string{ // for both 386 and AMD64
    54  	"JMP main.main(SB)",
    55  	"CALL main.Println(SB)",
    56  	"RET",
    57  }
    58  
    59  var amd64GnuNeed = []string{
    60  	"jmp",
    61  	"callq",
    62  	"cmpb",
    63  }
    64  
    65  var i386GnuNeed = []string{
    66  	"jmp",
    67  	"call",
    68  	"cmp",
    69  }
    70  
    71  var armNeed = []string{
    72  	"B main.main(SB)",
    73  	"BL main.Println(SB)",
    74  	"RET",
    75  }
    76  
    77  var arm64Need = []string{
    78  	"JMP main.main(SB)",
    79  	"CALL main.Println(SB)",
    80  	"RET",
    81  }
    82  
    83  var armGnuNeed = []string{ // for both ARM and AMR64
    84  	"ldr",
    85  	"bl",
    86  	"cmp",
    87  }
    88  
    89  var ppcNeed = []string{
    90  	"BR main.main(SB)",
    91  	"CALL main.Println(SB)",
    92  	"RET",
    93  }
    94  
    95  var ppcPIENeed = []string{
    96  	"BR",
    97  	"CALL",
    98  	"RET",
    99  }
   100  
   101  var ppcGnuNeed = []string{
   102  	"mflr",
   103  	"lbz",
   104  	"beq",
   105  }
   106  
   107  func mustHaveDisasm(t *testing.T) {
   108  	switch runtime.GOARCH {
   109  	case "loong64":
   110  		t.Skipf("skipping on %s", runtime.GOARCH)
   111  	case "mips", "mipsle", "mips64", "mips64le":
   112  		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
   113  	case "riscv64":
   114  		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
   115  	case "s390x":
   116  		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
   117  	}
   118  }
   119  
   120  var target = flag.String("target", "", "test disassembly of `goos/goarch` binary")
   121  
   122  // objdump is fully cross platform: it can handle binaries
   123  // from any known operating system and architecture.
   124  // We could in principle add binaries to testdata and check
   125  // all the supported systems during this test. However, the
   126  // binaries would be about 1 MB each, and we don't want to
   127  // add that much junk to the hg repository. Instead, build a
   128  // binary for the current system (only) and test that objdump
   129  // can handle that one.
   130  
   131  func testDisasm(t *testing.T, srcfname string, printCode bool, printGnuAsm bool, flags ...string) {
   132  	mustHaveDisasm(t)
   133  	goarch := runtime.GOARCH
   134  	if *target != "" {
   135  		f := strings.Split(*target, "/")
   136  		if len(f) != 2 {
   137  			t.Fatalf("-target argument must be goos/goarch")
   138  		}
   139  		defer os.Setenv("GOOS", os.Getenv("GOOS"))
   140  		defer os.Setenv("GOARCH", os.Getenv("GOARCH"))
   141  		os.Setenv("GOOS", f[0])
   142  		os.Setenv("GOARCH", f[1])
   143  		goarch = f[1]
   144  	}
   145  
   146  	hash := notsha256.Sum256([]byte(fmt.Sprintf("%v-%v-%v-%v", srcfname, flags, printCode, printGnuAsm)))
   147  	tmp := t.TempDir()
   148  	hello := filepath.Join(tmp, fmt.Sprintf("hello-%x.exe", hash))
   149  	args := []string{"build", "-o", hello}
   150  	args = append(args, flags...)
   151  	args = append(args, srcfname)
   152  	cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
   153  	// "Bad line" bug #36683 is sensitive to being run in the source directory.
   154  	cmd.Dir = "testdata"
   155  	// Ensure that the source file location embedded in the binary matches our
   156  	// actual current GOROOT, instead of GOROOT_FINAL if set.
   157  	cmd.Env = append(os.Environ(), "GOROOT_FINAL=")
   158  	t.Logf("Running %v", cmd.Args)
   159  	out, err := cmd.CombinedOutput()
   160  	if err != nil {
   161  		t.Fatalf("go build %s: %v\n%s", srcfname, err, out)
   162  	}
   163  	need := []string{
   164  		"TEXT main.main(SB)",
   165  	}
   166  
   167  	if printCode {
   168  		need = append(need, `	Println("hello, world")`)
   169  	} else {
   170  		need = append(need, srcfname+":6")
   171  	}
   172  
   173  	switch goarch {
   174  	case "amd64", "386":
   175  		need = append(need, x86Need...)
   176  	case "arm":
   177  		need = append(need, armNeed...)
   178  	case "arm64":
   179  		need = append(need, arm64Need...)
   180  	case "ppc64", "ppc64le":
   181  		var pie bool
   182  		for _, flag := range flags {
   183  			if flag == "-buildmode=pie" {
   184  				pie = true
   185  				break
   186  			}
   187  		}
   188  		if pie {
   189  			// In PPC64 PIE binaries we use a "local entry point" which is
   190  			// function symbol address + 8. Currently we don't symbolize that.
   191  			// Expect a different output.
   192  			need = append(need, ppcPIENeed...)
   193  		} else {
   194  			need = append(need, ppcNeed...)
   195  		}
   196  	}
   197  
   198  	if printGnuAsm {
   199  		switch goarch {
   200  		case "amd64":
   201  			need = append(need, amd64GnuNeed...)
   202  		case "386":
   203  			need = append(need, i386GnuNeed...)
   204  		case "arm", "arm64":
   205  			need = append(need, armGnuNeed...)
   206  		case "ppc64", "ppc64le":
   207  			need = append(need, ppcGnuNeed...)
   208  		}
   209  	}
   210  	args = []string{
   211  		"-s", "main.main",
   212  		hello,
   213  	}
   214  
   215  	if printCode {
   216  		args = append([]string{"-S"}, args...)
   217  	}
   218  
   219  	if printGnuAsm {
   220  		args = append([]string{"-gnu"}, args...)
   221  	}
   222  	cmd = testenv.Command(t, objdumpPath(t), args...)
   223  	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
   224  	out, err = cmd.CombinedOutput()
   225  	t.Logf("Running %v", cmd.Args)
   226  
   227  	if err != nil {
   228  		exename := srcfname[:len(srcfname)-len(filepath.Ext(srcfname))] + ".exe"
   229  		t.Fatalf("objdump %q: %v\n%s", exename, err, out)
   230  	}
   231  
   232  	text := string(out)
   233  	ok := true
   234  	for _, s := range need {
   235  		if !strings.Contains(text, s) {
   236  			t.Errorf("disassembly missing '%s'", s)
   237  			ok = false
   238  		}
   239  	}
   240  	if goarch == "386" {
   241  		if strings.Contains(text, "(IP)") {
   242  			t.Errorf("disassembly contains PC-Relative addressing on 386")
   243  			ok = false
   244  		}
   245  	}
   246  
   247  	if !ok || testing.Verbose() {
   248  		t.Logf("full disassembly:\n%s", text)
   249  	}
   250  }
   251  
   252  func testGoAndCgoDisasm(t *testing.T, printCode bool, printGnuAsm bool) {
   253  	t.Parallel()
   254  	testDisasm(t, "fmthello.go", printCode, printGnuAsm)
   255  	if testenv.HasCGO() {
   256  		testDisasm(t, "fmthellocgo.go", printCode, printGnuAsm)
   257  	}
   258  }
   259  
   260  func TestDisasm(t *testing.T) {
   261  	testGoAndCgoDisasm(t, false, false)
   262  }
   263  
   264  func TestDisasmCode(t *testing.T) {
   265  	testGoAndCgoDisasm(t, true, false)
   266  }
   267  
   268  func TestDisasmGnuAsm(t *testing.T) {
   269  	testGoAndCgoDisasm(t, false, true)
   270  }
   271  
   272  func TestDisasmExtld(t *testing.T) {
   273  	testenv.MustHaveCGO(t)
   274  	switch runtime.GOOS {
   275  	case "plan9":
   276  		t.Skipf("skipping on %s", runtime.GOOS)
   277  	}
   278  	t.Parallel()
   279  	testDisasm(t, "fmthello.go", false, false, "-ldflags=-linkmode=external")
   280  }
   281  
   282  func TestDisasmPIE(t *testing.T) {
   283  	if !platform.BuildModeSupported("gc", "pie", runtime.GOOS, runtime.GOARCH) {
   284  		t.Skipf("skipping on %s/%s, PIE buildmode not supported", runtime.GOOS, runtime.GOARCH)
   285  	}
   286  	if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
   287  		// require cgo on platforms that PIE needs external linking
   288  		testenv.MustHaveCGO(t)
   289  	}
   290  	t.Parallel()
   291  	testDisasm(t, "fmthello.go", false, false, "-buildmode=pie")
   292  }
   293  
   294  func TestDisasmGoobj(t *testing.T) {
   295  	mustHaveDisasm(t)
   296  	testenv.MustHaveGoBuild(t)
   297  
   298  	tmp := t.TempDir()
   299  
   300  	importcfgfile := filepath.Join(tmp, "hello.importcfg")
   301  	testenv.WriteImportcfg(t, importcfgfile, nil, "testdata/fmthello.go")
   302  
   303  	hello := filepath.Join(tmp, "hello.o")
   304  	args := []string{"tool", "compile", "-p=main", "-importcfg=" + importcfgfile, "-o", hello}
   305  	args = append(args, "testdata/fmthello.go")
   306  	out, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput()
   307  	if err != nil {
   308  		t.Fatalf("go tool compile fmthello.go: %v\n%s", err, out)
   309  	}
   310  	need := []string{
   311  		"main(SB)",
   312  		"fmthello.go:6",
   313  	}
   314  
   315  	args = []string{
   316  		"-s", "main",
   317  		hello,
   318  	}
   319  
   320  	out, err = testenv.Command(t, objdumpPath(t), args...).CombinedOutput()
   321  	if err != nil {
   322  		t.Fatalf("objdump fmthello.o: %v\n%s", err, out)
   323  	}
   324  
   325  	text := string(out)
   326  	ok := true
   327  	for _, s := range need {
   328  		if !strings.Contains(text, s) {
   329  			t.Errorf("disassembly missing '%s'", s)
   330  			ok = false
   331  		}
   332  	}
   333  	if runtime.GOARCH == "386" {
   334  		if strings.Contains(text, "(IP)") {
   335  			t.Errorf("disassembly contains PC-Relative addressing on 386")
   336  			ok = false
   337  		}
   338  	}
   339  	if !ok {
   340  		t.Logf("full disassembly:\n%s", text)
   341  	}
   342  }
   343  
   344  func TestGoobjFileNumber(t *testing.T) {
   345  	// Test that file table in Go object file is parsed correctly.
   346  	testenv.MustHaveGoBuild(t)
   347  	mustHaveDisasm(t)
   348  
   349  	t.Parallel()
   350  
   351  	tmp := t.TempDir()
   352  
   353  	obj := filepath.Join(tmp, "p.a")
   354  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", obj)
   355  	cmd.Dir = filepath.Join("testdata/testfilenum")
   356  	out, err := cmd.CombinedOutput()
   357  	if err != nil {
   358  		t.Fatalf("build failed: %v\n%s", err, out)
   359  	}
   360  
   361  	cmd = testenv.Command(t, objdumpPath(t), obj)
   362  	out, err = cmd.CombinedOutput()
   363  	if err != nil {
   364  		t.Fatalf("objdump failed: %v\n%s", err, out)
   365  	}
   366  
   367  	text := string(out)
   368  	for _, s := range []string{"a.go", "b.go", "c.go"} {
   369  		if !strings.Contains(text, s) {
   370  			t.Errorf("output missing '%s'", s)
   371  		}
   372  	}
   373  
   374  	if t.Failed() {
   375  		t.Logf("output:\n%s", text)
   376  	}
   377  }
   378  
   379  func TestGoObjOtherVersion(t *testing.T) {
   380  	testenv.MustHaveExec(t)
   381  	t.Parallel()
   382  
   383  	obj := filepath.Join("testdata", "go116.o")
   384  	cmd := testenv.Command(t, objdumpPath(t), obj)
   385  	out, err := cmd.CombinedOutput()
   386  	if err == nil {
   387  		t.Fatalf("objdump go116.o succeeded unexpectedly")
   388  	}
   389  	if !strings.Contains(string(out), "go object of a different version") {
   390  		t.Errorf("unexpected error message:\n%s", out)
   391  	}
   392  }
   393  

View as plain text