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

View as plain text