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  func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) {
    25  	testenv.MustHaveCGO(t)
    26  	testenv.MustHaveGoBuild(t)
    27  
    28  	if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
    29  		t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
    30  	}
    31  
    32  	t.Parallel()
    33  
    34  	for _, prog := range []string{"testprog", "testprogcgo"} {
    35  		prog := prog
    36  		expectDWARF := expectDWARF
    37  		if runtime.GOOS == "aix" && prog == "testprogcgo" {
    38  			extld := os.Getenv("CC")
    39  			if extld == "" {
    40  				extld = "gcc"
    41  			}
    42  			extldArgs, err := quoted.Split(extld)
    43  			if err != nil {
    44  				t.Fatal(err)
    45  			}
    46  			expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
    47  			if err != nil {
    48  				t.Fatal(err)
    49  			}
    50  		}
    51  
    52  		t.Run(prog, func(t *testing.T) {
    53  			t.Parallel()
    54  
    55  			tmpDir := t.TempDir()
    56  
    57  			exe := filepath.Join(tmpDir, prog+".exe")
    58  			dir := "../../runtime/testdata/" + prog
    59  			cmd := goCmd(t, "build", "-o", exe)
    60  			if buildmode != "" {
    61  				cmd.Args = append(cmd.Args, "-buildmode", buildmode)
    62  			}
    63  			cmd.Args = append(cmd.Args, dir)
    64  			cmd.Env = append(cmd.Env, env...)
    65  			cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459
    66  			out, err := cmd.CombinedOutput()
    67  			if err != nil {
    68  				t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
    69  			}
    70  
    71  			if buildmode == "c-archive" {
    72  				// Extract the archive and use the go.o object within.
    73  				ar := os.Getenv("AR")
    74  				if ar == "" {
    75  					ar = "ar"
    76  				}
    77  				cmd := testenv.Command(t, ar, "-x", exe)
    78  				cmd.Dir = tmpDir
    79  				if out, err := cmd.CombinedOutput(); err != nil {
    80  					t.Fatalf("%s -x %s: %v\n%s", ar, exe, err, out)
    81  				}
    82  				exe = filepath.Join(tmpDir, "go.o")
    83  			}
    84  
    85  			darwinSymbolTestIsTooFlaky := true // Turn this off, it is too flaky -- See #32218
    86  			if runtime.GOOS == "darwin" && !darwinSymbolTestIsTooFlaky {
    87  				if _, err = exec.LookPath("symbols"); err == nil {
    88  					// Ensure Apple's tooling can parse our object for symbols.
    89  					out, err = testenv.Command(t, "symbols", exe).CombinedOutput()
    90  					if err != nil {
    91  						t.Fatalf("symbols %v: %v: %s", filepath.Base(exe), err, out)
    92  					} else {
    93  						if bytes.HasPrefix(out, []byte("Unable to find file")) {
    94  							// This failure will cause the App Store to reject our binaries.
    95  							t.Fatalf("symbols %v: failed to parse file", filepath.Base(exe))
    96  						} else if bytes.Contains(out, []byte(", Empty]")) {
    97  							t.Fatalf("symbols %v: parsed as empty", filepath.Base(exe))
    98  						}
    99  					}
   100  				}
   101  			}
   102  
   103  			f, err := objfile.Open(exe)
   104  			if err != nil {
   105  				t.Fatal(err)
   106  			}
   107  			defer f.Close()
   108  
   109  			syms, err := f.Symbols()
   110  			if err != nil {
   111  				t.Fatal(err)
   112  			}
   113  
   114  			var addr uint64
   115  			for _, sym := range syms {
   116  				if sym.Name == "main.main" {
   117  					addr = sym.Addr
   118  					break
   119  				}
   120  			}
   121  			if addr == 0 {
   122  				t.Fatal("cannot find main.main in symbols")
   123  			}
   124  
   125  			d, err := f.DWARF()
   126  			if err != nil {
   127  				if expectDWARF {
   128  					t.Fatal(err)
   129  				}
   130  				return
   131  			} else {
   132  				if !expectDWARF {
   133  					t.Fatal("unexpected DWARF section")
   134  				}
   135  			}
   136  
   137  			// TODO: We'd like to use filepath.Join here.
   138  			// Also related: golang.org/issue/19784.
   139  			wantFile := path.Join(prog, "main.go")
   140  			wantLine := 24
   141  			r := d.Reader()
   142  			entry, err := r.SeekPC(addr)
   143  			if err != nil {
   144  				t.Fatal(err)
   145  			}
   146  			lr, err := d.LineReader(entry)
   147  			if err != nil {
   148  				t.Fatal(err)
   149  			}
   150  			var line dwarf.LineEntry
   151  			if err := lr.SeekPC(addr, &line); err == dwarf.ErrUnknownPC {
   152  				t.Fatalf("did not find file:line for %#x (main.main)", addr)
   153  			} else if err != nil {
   154  				t.Fatal(err)
   155  			}
   156  			if !strings.HasSuffix(line.File.Name, wantFile) || line.Line != wantLine {
   157  				t.Errorf("%#x is %s:%d, want %s:%d", addr, line.File.Name, line.Line, filepath.Join("...", wantFile), wantLine)
   158  			}
   159  
   160  			if buildmode != "c-archive" {
   161  				testModuledata(t, d)
   162  			}
   163  		})
   164  	}
   165  }
   166  
   167  // testModuledata makes sure that runtime.firstmoduledata exists
   168  // and has a type. Issue #76731.
   169  func testModuledata(t *testing.T, d *dwarf.Data) {
   170  	const symName = "runtime.firstmoduledata"
   171  
   172  	r := d.Reader()
   173  	for {
   174  		e, err := r.Next()
   175  		if err != nil {
   176  			t.Error(err)
   177  			return
   178  		}
   179  		if e == nil {
   180  			t.Errorf("did not find DWARF entry for %s", symName)
   181  			return
   182  		}
   183  
   184  		switch e.Tag {
   185  		case dwarf.TagVariable:
   186  			// carry on after switch
   187  		case dwarf.TagCompileUnit, dwarf.TagSubprogram:
   188  			continue
   189  		default:
   190  			r.SkipChildren()
   191  			continue
   192  		}
   193  
   194  		nameIdx, typeIdx := -1, -1
   195  		for i := range e.Field {
   196  			f := &e.Field[i]
   197  			switch f.Attr {
   198  			case dwarf.AttrName:
   199  				nameIdx = i
   200  			case dwarf.AttrType:
   201  				typeIdx = i
   202  			}
   203  		}
   204  		if nameIdx == -1 {
   205  			// unnamed variable?
   206  			r.SkipChildren()
   207  			continue
   208  		}
   209  		nameStr, ok := e.Field[nameIdx].Val.(string)
   210  		if !ok {
   211  			// variable name is not a string?
   212  			r.SkipChildren()
   213  			continue
   214  		}
   215  		if nameStr != symName {
   216  			r.SkipChildren()
   217  			continue
   218  		}
   219  
   220  		if typeIdx == -1 {
   221  			t.Errorf("%s has no DWARF type", symName)
   222  			return
   223  		}
   224  		off, ok := e.Field[typeIdx].Val.(dwarf.Offset)
   225  		if !ok {
   226  			t.Errorf("unexpected Go type %T for DWARF type for %s; expected %T", e.Field[typeIdx].Val, symName, dwarf.Offset(0))
   227  			return
   228  		}
   229  
   230  		typeInfo, err := d.Type(off)
   231  		if err != nil {
   232  			t.Error(err)
   233  			return
   234  		}
   235  
   236  		typeName := typeInfo.Common().Name
   237  		if want := "runtime.moduledata"; typeName != want {
   238  			t.Errorf("type of %s is %s, expected %s", symName, typeName, want)
   239  		}
   240  		for {
   241  			typedef, ok := typeInfo.(*dwarf.TypedefType)
   242  			if !ok {
   243  				break
   244  			}
   245  			typeInfo = typedef.Type
   246  		}
   247  		if _, ok := typeInfo.(*dwarf.StructType); !ok {
   248  			t.Errorf("type of %s is %T, expected %T", symName, typeInfo, dwarf.StructType{})
   249  		}
   250  
   251  		return
   252  	}
   253  }
   254  
   255  func TestDWARF(t *testing.T) {
   256  	testDWARF(t, "", true)
   257  	if !testing.Short() {
   258  		if runtime.GOOS == "windows" {
   259  			t.Skip("skipping Windows/c-archive; see Issue 35512 for more.")
   260  		}
   261  		if !platform.BuildModeSupported(runtime.Compiler, "c-archive", runtime.GOOS, runtime.GOARCH) {
   262  			t.Skipf("skipping c-archive test on unsupported platform %s-%s", runtime.GOOS, runtime.GOARCH)
   263  		}
   264  		t.Run("c-archive", func(t *testing.T) {
   265  			testDWARF(t, "c-archive", true)
   266  		})
   267  	}
   268  }
   269  
   270  func TestDWARFiOS(t *testing.T) {
   271  	// Normally we run TestDWARF on native platform. But on iOS we don't have
   272  	// go build, so we do this test with a cross build.
   273  	// Only run this on darwin/amd64, where we can cross build for iOS.
   274  	if testing.Short() {
   275  		t.Skip("skipping in short mode")
   276  	}
   277  	if runtime.GOARCH != "amd64" || runtime.GOOS != "darwin" {
   278  		t.Skip("skipping on non-darwin/amd64 platform")
   279  	}
   280  	if err := testenv.Command(t, "xcrun", "--help").Run(); err != nil {
   281  		t.Skipf("error running xcrun, required for iOS cross build: %v", err)
   282  	}
   283  	// Check to see if the ios tools are installed. It's possible to have the command line tools
   284  	// installed without the iOS sdk.
   285  	if output, err := testenv.Command(t, "xcodebuild", "-showsdks").CombinedOutput(); err != nil {
   286  		t.Skipf("error running xcodebuild, required for iOS cross build: %v", err)
   287  	} else if !strings.Contains(string(output), "iOS SDK") {
   288  		t.Skipf("iOS SDK not detected.")
   289  	}
   290  	cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh"
   291  	// iOS doesn't allow unmapped segments, so iOS executables don't have DWARF.
   292  	t.Run("exe", func(t *testing.T) {
   293  		testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
   294  	})
   295  	// However, c-archive iOS objects have embedded DWARF.
   296  	t.Run("c-archive", func(t *testing.T) {
   297  		testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
   298  	})
   299  }
   300  
   301  // This test ensures that variables promoted to the heap, specifically
   302  // function return parameters, have correct location lists generated.
   303  //
   304  // TODO(deparker): This test is intentionally limited to GOOS=="linux"
   305  // and scoped to net.sendFile, which was the function reported originally in
   306  // issue #65405. There is relevant discussion in https://go-review.googlesource.com/c/go/+/684377
   307  // pertaining to these limitations. There are other missing location lists which must be fixed
   308  // particularly in functions where `linkname` is involved.
   309  func TestDWARFLocationList(t *testing.T) {
   310  	if runtime.GOOS != "linux" {
   311  		t.Skip("skipping test on non-linux OS")
   312  	}
   313  	testenv.MustHaveCGO(t)
   314  	testenv.MustHaveGoBuild(t)
   315  
   316  	if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
   317  		t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
   318  	}
   319  
   320  	t.Parallel()
   321  
   322  	tmpDir := t.TempDir()
   323  	exe := filepath.Join(tmpDir, "issue65405.exe")
   324  	dir := "./testdata/dwarf/issue65405"
   325  
   326  	cmd := goCmd(t, "build", "-gcflags=all=-N -l", "-o", exe, dir)
   327  	cmd.Env = append(cmd.Env, "CGO_CFLAGS=")
   328  	out, err := cmd.CombinedOutput()
   329  	if err != nil {
   330  		t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
   331  	}
   332  
   333  	f, err := objfile.Open(exe)
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	defer f.Close()
   338  
   339  	d, err := f.DWARF()
   340  	if err != nil {
   341  		t.Fatal(err)
   342  	}
   343  
   344  	// Find the net.sendFile function and check its return parameter location list
   345  	reader := d.Reader()
   346  
   347  	for {
   348  		entry, err := reader.Next()
   349  		if err != nil {
   350  			t.Fatal(err)
   351  		}
   352  		if entry == nil {
   353  			break
   354  		}
   355  
   356  		// Look for the net.sendFile subprogram
   357  		if entry.Tag == dwarf.TagSubprogram {
   358  			fnName, ok := entry.Val(dwarf.AttrName).(string)
   359  			if !ok || fnName != "net.sendFile" {
   360  				reader.SkipChildren()
   361  				continue
   362  			}
   363  
   364  			for {
   365  				paramEntry, err := reader.Next()
   366  				if err != nil {
   367  					t.Fatal(err)
   368  				}
   369  				if paramEntry == nil || paramEntry.Tag == 0 {
   370  					break
   371  				}
   372  
   373  				if paramEntry.Tag == dwarf.TagFormalParameter {
   374  					paramName, _ := paramEntry.Val(dwarf.AttrName).(string)
   375  
   376  					// Check if this parameter has a location attribute
   377  					if loc := paramEntry.Val(dwarf.AttrLocation); loc != nil {
   378  						switch locData := loc.(type) {
   379  						case []byte:
   380  							if len(locData) == 0 {
   381  								t.Errorf("%s return parameter %q has empty location list", fnName, paramName)
   382  								return
   383  							}
   384  						case int64:
   385  							// Location list offset - this means it has a location list
   386  							if locData == 0 {
   387  								t.Errorf("%s return parameter %q has zero location list offset", fnName, paramName)
   388  								return
   389  							}
   390  						default:
   391  							t.Errorf("%s return parameter %q has unexpected location type %T: %v", fnName, paramName, locData, locData)
   392  						}
   393  					} else {
   394  						t.Errorf("%s return parameter %q has no location attribute", fnName, paramName)
   395  					}
   396  				}
   397  			}
   398  		}
   399  	}
   400  }
   401  
   402  func TestFlagW(t *testing.T) {
   403  	testenv.MustHaveGoBuild(t)
   404  	if runtime.GOOS == "aix" {
   405  		t.Skip("internal/xcoff cannot parse file without symbol table")
   406  	}
   407  	if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
   408  		t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
   409  	}
   410  
   411  	t.Parallel()
   412  
   413  	tmpdir := t.TempDir()
   414  	src := filepath.Join(tmpdir, "a.go")
   415  	err := os.WriteFile(src, []byte(helloSrc), 0666)
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  
   420  	type testCase struct {
   421  		flag      string
   422  		wantDWARF bool
   423  	}
   424  	tests := []testCase{
   425  		{"-w", false},     // -w flag disables DWARF
   426  		{"-s", false},     // -s implies -w
   427  		{"-s -w=0", true}, // -w=0 negates the implied -w
   428  	}
   429  	if testenv.HasCGO() && runtime.GOOS != "solaris" { // Solaris linker doesn't support the -S flag
   430  		tests = append(tests,
   431  			testCase{"-w -linkmode=external", false},
   432  			testCase{"-s -linkmode=external", false},
   433  			// Some external linkers don't have a way to preserve DWARF
   434  			// without emitting the symbol table. Skip this case for now.
   435  			// I suppose we can post- process, e.g. with objcopy.
   436  			//testCase{"-s -w=0 -linkmode=external", true},
   437  		)
   438  	}
   439  
   440  	for _, test := range tests {
   441  		name := strings.ReplaceAll(test.flag, " ", "_")
   442  		t.Run(name, func(t *testing.T) {
   443  			ldflags := "-ldflags=" + test.flag
   444  			exe := filepath.Join(t.TempDir(), "a.exe")
   445  			cmd := goCmd(t, "build", ldflags, "-o", exe, src)
   446  			out, err := cmd.CombinedOutput()
   447  			if err != nil {
   448  				t.Fatalf("build failed: %v\n%s", err, out)
   449  			}
   450  
   451  			f, err := objfile.Open(exe)
   452  			if err != nil {
   453  				t.Fatal(err)
   454  			}
   455  			defer f.Close()
   456  
   457  			d, err := f.DWARF()
   458  			if test.wantDWARF {
   459  				if err != nil {
   460  					t.Errorf("want binary with DWARF, got error %v", err)
   461  				}
   462  			} else {
   463  				if d != nil {
   464  					t.Errorf("want binary with no DWARF, got DWARF")
   465  				}
   466  			}
   467  		})
   468  	}
   469  }
   470  

View as plain text