Source file src/cmd/link/dwarf_test.go

     1  // Copyright 2017 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  	"bytes"
     9  	cmddwarf "cmd/internal/dwarf"
    10  	"cmd/internal/objfile"
    11  	"cmd/internal/quoted"
    12  	"debug/dwarf"
    13  	"internal/testenv"
    14  	"os"
    15  	"os/exec"
    16  	"path"
    17  	"path/filepath"
    18  	"runtime"
    19  	"strings"
    20  	"testing"
    21  )
    22  
    23  // TestMain allows this test binary to run as a -toolexec wrapper for the 'go'
    24  // command. If LINK_TEST_TOOLEXEC is set, TestMain runs the binary as if it were
    25  // cmd/link, and otherwise runs the requested tool as a subprocess.
    26  //
    27  // This allows the test to verify the behavior of the current contents of the
    28  // cmd/link package even if the installed cmd/link binary is stale.
    29  func TestMain(m *testing.M) {
    30  	if os.Getenv("LINK_TEST_TOOLEXEC") == "" {
    31  		// Not running as a -toolexec wrapper. Just run the tests.
    32  		os.Exit(m.Run())
    33  	}
    34  
    35  	if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" {
    36  		// Running as a -toolexec linker, and the tool is cmd/link.
    37  		// Substitute this test binary for the linker.
    38  		os.Args = os.Args[1:]
    39  		main()
    40  		os.Exit(0)
    41  	}
    42  
    43  	cmd := exec.Command(os.Args[1], os.Args[2:]...)
    44  	cmd.Stdin = os.Stdin
    45  	cmd.Stdout = os.Stdout
    46  	cmd.Stderr = os.Stderr
    47  	if err := cmd.Run(); err != nil {
    48  		os.Exit(1)
    49  	}
    50  	os.Exit(0)
    51  }
    52  
    53  func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) {
    54  	testenv.MustHaveCGO(t)
    55  	testenv.MustHaveGoBuild(t)
    56  
    57  	if runtime.GOOS == "plan9" {
    58  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
    59  	}
    60  
    61  	t.Parallel()
    62  
    63  	for _, prog := range []string{"testprog", "testprogcgo"} {
    64  		prog := prog
    65  		expectDWARF := expectDWARF
    66  		if runtime.GOOS == "aix" && prog == "testprogcgo" {
    67  			extld := os.Getenv("CC")
    68  			if extld == "" {
    69  				extld = "gcc"
    70  			}
    71  			extldArgs, err := quoted.Split(extld)
    72  			if err != nil {
    73  				t.Fatal(err)
    74  			}
    75  			expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
    76  			if err != nil {
    77  				t.Fatal(err)
    78  			}
    79  		}
    80  
    81  		t.Run(prog, func(t *testing.T) {
    82  			t.Parallel()
    83  
    84  			tmpDir := t.TempDir()
    85  
    86  			exe := filepath.Join(tmpDir, prog+".exe")
    87  			dir := "../../runtime/testdata/" + prog
    88  			cmd := exec.Command(testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-o", exe)
    89  			if buildmode != "" {
    90  				cmd.Args = append(cmd.Args, "-buildmode", buildmode)
    91  			}
    92  			cmd.Args = append(cmd.Args, dir)
    93  			cmd.Env = append(os.Environ(), env...)
    94  			cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459
    95  			cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1")
    96  			out, err := cmd.CombinedOutput()
    97  			if err != nil {
    98  				t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
    99  			}
   100  
   101  			if buildmode == "c-archive" {
   102  				// Extract the archive and use the go.o object within.
   103  				cmd := exec.Command("ar", "-x", exe)
   104  				cmd.Dir = tmpDir
   105  				if out, err := cmd.CombinedOutput(); err != nil {
   106  					t.Fatalf("ar -x %s: %v\n%s", exe, err, out)
   107  				}
   108  				exe = filepath.Join(tmpDir, "go.o")
   109  			}
   110  
   111  			darwinSymbolTestIsTooFlaky := true // Turn this off, it is too flaky -- See #32218
   112  			if runtime.GOOS == "darwin" && !darwinSymbolTestIsTooFlaky {
   113  				if _, err = exec.LookPath("symbols"); err == nil {
   114  					// Ensure Apple's tooling can parse our object for symbols.
   115  					out, err = exec.Command("symbols", exe).CombinedOutput()
   116  					if err != nil {
   117  						t.Fatalf("symbols %v: %v: %s", filepath.Base(exe), err, out)
   118  					} else {
   119  						if bytes.HasPrefix(out, []byte("Unable to find file")) {
   120  							// This failure will cause the App Store to reject our binaries.
   121  							t.Fatalf("symbols %v: failed to parse file", filepath.Base(exe))
   122  						} else if bytes.Contains(out, []byte(", Empty]")) {
   123  							t.Fatalf("symbols %v: parsed as empty", filepath.Base(exe))
   124  						}
   125  					}
   126  				}
   127  			}
   128  
   129  			f, err := objfile.Open(exe)
   130  			if err != nil {
   131  				t.Fatal(err)
   132  			}
   133  			defer f.Close()
   134  
   135  			syms, err := f.Symbols()
   136  			if err != nil {
   137  				t.Fatal(err)
   138  			}
   139  
   140  			var addr uint64
   141  			for _, sym := range syms {
   142  				if sym.Name == "main.main" {
   143  					addr = sym.Addr
   144  					break
   145  				}
   146  			}
   147  			if addr == 0 {
   148  				t.Fatal("cannot find main.main in symbols")
   149  			}
   150  
   151  			d, err := f.DWARF()
   152  			if err != nil {
   153  				if expectDWARF {
   154  					t.Fatal(err)
   155  				}
   156  				return
   157  			} else {
   158  				if !expectDWARF {
   159  					t.Fatal("unexpected DWARF section")
   160  				}
   161  			}
   162  
   163  			// TODO: We'd like to use filepath.Join here.
   164  			// Also related: golang.org/issue/19784.
   165  			wantFile := path.Join(prog, "main.go")
   166  			wantLine := 24
   167  			r := d.Reader()
   168  			entry, err := r.SeekPC(addr)
   169  			if err != nil {
   170  				t.Fatal(err)
   171  			}
   172  			lr, err := d.LineReader(entry)
   173  			if err != nil {
   174  				t.Fatal(err)
   175  			}
   176  			var line dwarf.LineEntry
   177  			if err := lr.SeekPC(addr, &line); err == dwarf.ErrUnknownPC {
   178  				t.Fatalf("did not find file:line for %#x (main.main)", addr)
   179  			} else if err != nil {
   180  				t.Fatal(err)
   181  			}
   182  			if !strings.HasSuffix(line.File.Name, wantFile) || line.Line != wantLine {
   183  				t.Errorf("%#x is %s:%d, want %s:%d", addr, line.File.Name, line.Line, filepath.Join("...", wantFile), wantLine)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func TestDWARF(t *testing.T) {
   190  	testDWARF(t, "", true)
   191  	if !testing.Short() {
   192  		if runtime.GOOS == "windows" {
   193  			t.Skip("skipping Windows/c-archive; see Issue 35512 for more.")
   194  		}
   195  		t.Run("c-archive", func(t *testing.T) {
   196  			testDWARF(t, "c-archive", true)
   197  		})
   198  	}
   199  }
   200  
   201  func TestDWARFiOS(t *testing.T) {
   202  	// Normally we run TestDWARF on native platform. But on iOS we don't have
   203  	// go build, so we do this test with a cross build.
   204  	// Only run this on darwin/amd64, where we can cross build for iOS.
   205  	if testing.Short() {
   206  		t.Skip("skipping in short mode")
   207  	}
   208  	if runtime.GOARCH != "amd64" || runtime.GOOS != "darwin" {
   209  		t.Skip("skipping on non-darwin/amd64 platform")
   210  	}
   211  	if err := exec.Command("xcrun", "--help").Run(); err != nil {
   212  		t.Skipf("error running xcrun, required for iOS cross build: %v", err)
   213  	}
   214  	// Check to see if the ios tools are installed. It's possible to have the command line tools
   215  	// installed without the iOS sdk.
   216  	if output, err := exec.Command("xcodebuild", "-showsdks").CombinedOutput(); err != nil {
   217  		t.Skipf("error running xcodebuild, required for iOS cross build: %v", err)
   218  	} else if !strings.Contains(string(output), "iOS SDK") {
   219  		t.Skipf("iOS SDK not detected.")
   220  	}
   221  	cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh"
   222  	// iOS doesn't allow unmapped segments, so iOS executables don't have DWARF.
   223  	t.Run("exe", func(t *testing.T) {
   224  		testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
   225  	})
   226  	// However, c-archive iOS objects have embedded DWARF.
   227  	t.Run("c-archive", func(t *testing.T) {
   228  		testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
   229  	})
   230  }
   231  

View as plain text