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

View as plain text