Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/link/dwarf_test.go

Documentation: cmd/link

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

View as plain text