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

View as plain text