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

View as plain text