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

View as plain text