...
Run Format

Source file src/cmd/link/internal/ld/dwarf_test.go

Documentation: cmd/link/internal/ld

     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 ld
     6  
     7  import (
     8  	objfilepkg "cmd/internal/objfile" // renamed to avoid conflict with objfile function
     9  	"debug/dwarf"
    10  	"errors"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"io/ioutil"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"reflect"
    18  	"runtime"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  const (
    25  	DefaultOpt = "-gcflags="
    26  	NoOpt      = "-gcflags=-l -N"
    27  	OptInl4    = "-gcflags=-l=4"
    28  	OptAllInl4 = "-gcflags=all=-l=4"
    29  )
    30  
    31  func TestRuntimeTypesPresent(t *testing.T) {
    32  	testenv.MustHaveGoBuild(t)
    33  
    34  	if runtime.GOOS == "plan9" {
    35  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
    36  	}
    37  
    38  	dir, err := ioutil.TempDir("", "TestRuntimeTypesPresent")
    39  	if err != nil {
    40  		t.Fatalf("could not create directory: %v", err)
    41  	}
    42  	defer os.RemoveAll(dir)
    43  
    44  	f := gobuild(t, dir, `package main; func main() { }`, NoOpt)
    45  	defer f.Close()
    46  
    47  	dwarf, err := f.DWARF()
    48  	if err != nil {
    49  		t.Fatalf("error reading DWARF: %v", err)
    50  	}
    51  
    52  	want := map[string]bool{
    53  		"runtime._type":         true,
    54  		"runtime.arraytype":     true,
    55  		"runtime.chantype":      true,
    56  		"runtime.functype":      true,
    57  		"runtime.maptype":       true,
    58  		"runtime.ptrtype":       true,
    59  		"runtime.slicetype":     true,
    60  		"runtime.structtype":    true,
    61  		"runtime.interfacetype": true,
    62  		"runtime.itab":          true,
    63  		"runtime.imethod":       true,
    64  	}
    65  
    66  	found := findTypes(t, dwarf, want)
    67  	if len(found) != len(want) {
    68  		t.Errorf("found %v, want %v", found, want)
    69  	}
    70  }
    71  
    72  func findTypes(t *testing.T, dw *dwarf.Data, want map[string]bool) (found map[string]bool) {
    73  	found = make(map[string]bool)
    74  	rdr := dw.Reader()
    75  	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
    76  		if err != nil {
    77  			t.Fatalf("error reading DWARF: %v", err)
    78  		}
    79  		switch entry.Tag {
    80  		case dwarf.TagTypedef:
    81  			if name, ok := entry.Val(dwarf.AttrName).(string); ok && want[name] {
    82  				found[name] = true
    83  			}
    84  		}
    85  	}
    86  	return
    87  }
    88  
    89  type builtFile struct {
    90  	*objfilepkg.File
    91  	path string
    92  }
    93  
    94  func gobuild(t *testing.T, dir string, testfile string, gcflags string) *builtFile {
    95  	src := filepath.Join(dir, "test.go")
    96  	dst := filepath.Join(dir, "out.exe")
    97  
    98  	if err := ioutil.WriteFile(src, []byte(testfile), 0666); err != nil {
    99  		t.Fatal(err)
   100  	}
   101  
   102  	cmd := exec.Command(testenv.GoToolPath(t), "build", gcflags, "-o", dst, src)
   103  	if b, err := cmd.CombinedOutput(); err != nil {
   104  		t.Logf("build: %s\n", b)
   105  		t.Fatalf("build error: %v", err)
   106  	}
   107  
   108  	f, err := objfilepkg.Open(dst)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	return &builtFile{f, dst}
   113  }
   114  
   115  func envWithGoPathSet(gp string) []string {
   116  	env := os.Environ()
   117  	for i := 0; i < len(env); i++ {
   118  		if strings.HasPrefix(env[i], "GOPATH=") {
   119  			env[i] = "GOPATH=" + gp
   120  			return env
   121  		}
   122  	}
   123  	env = append(env, "GOPATH="+gp)
   124  	return env
   125  }
   126  
   127  // Similar to gobuild() above, but runs off a separate GOPATH environment
   128  
   129  func gobuildTestdata(t *testing.T, tdir string, gopathdir string, packtobuild string, gcflags string) *builtFile {
   130  	dst := filepath.Join(tdir, "out.exe")
   131  
   132  	// Run a build with an updated GOPATH
   133  	cmd := exec.Command(testenv.GoToolPath(t), "build", gcflags, "-o", dst, packtobuild)
   134  	cmd.Env = envWithGoPathSet(gopathdir)
   135  	if b, err := cmd.CombinedOutput(); err != nil {
   136  		t.Logf("build: %s\n", b)
   137  		t.Fatalf("build error: %v", err)
   138  	}
   139  
   140  	f, err := objfilepkg.Open(dst)
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	return &builtFile{f, dst}
   145  }
   146  
   147  func TestEmbeddedStructMarker(t *testing.T) {
   148  	testenv.MustHaveGoBuild(t)
   149  
   150  	if runtime.GOOS == "plan9" {
   151  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   152  	}
   153  
   154  	const prog = `
   155  package main
   156  
   157  import "fmt"
   158  
   159  type Foo struct { v int }
   160  type Bar struct {
   161  	Foo
   162  	name string
   163  }
   164  type Baz struct {
   165  	*Foo
   166  	name string
   167  }
   168  
   169  func main() {
   170  	bar := Bar{ Foo: Foo{v: 123}, name: "onetwothree"}
   171  	baz := Baz{ Foo: &bar.Foo, name: "123" }
   172  	fmt.Println(bar, baz)
   173  }`
   174  
   175  	want := map[string]map[string]bool{
   176  		"main.Foo": map[string]bool{"v": false},
   177  		"main.Bar": map[string]bool{"Foo": true, "name": false},
   178  		"main.Baz": map[string]bool{"Foo": true, "name": false},
   179  	}
   180  
   181  	dir, err := ioutil.TempDir("", "TestEmbeddedStructMarker")
   182  	if err != nil {
   183  		t.Fatalf("could not create directory: %v", err)
   184  	}
   185  	defer os.RemoveAll(dir)
   186  
   187  	f := gobuild(t, dir, prog, NoOpt)
   188  
   189  	defer f.Close()
   190  
   191  	d, err := f.DWARF()
   192  	if err != nil {
   193  		t.Fatalf("error reading DWARF: %v", err)
   194  	}
   195  
   196  	rdr := d.Reader()
   197  	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
   198  		if err != nil {
   199  			t.Fatalf("error reading DWARF: %v", err)
   200  		}
   201  		switch entry.Tag {
   202  		case dwarf.TagStructType:
   203  			name := entry.Val(dwarf.AttrName).(string)
   204  			wantMembers := want[name]
   205  			if wantMembers == nil {
   206  				continue
   207  			}
   208  			gotMembers, err := findMembers(rdr)
   209  			if err != nil {
   210  				t.Fatalf("error reading DWARF: %v", err)
   211  			}
   212  
   213  			if !reflect.DeepEqual(gotMembers, wantMembers) {
   214  				t.Errorf("type %v: got map[member]embedded = %+v, want %+v", name, wantMembers, gotMembers)
   215  			}
   216  			delete(want, name)
   217  		}
   218  	}
   219  	if len(want) != 0 {
   220  		t.Errorf("failed to check all expected types: missing types = %+v", want)
   221  	}
   222  }
   223  
   224  func findMembers(rdr *dwarf.Reader) (map[string]bool, error) {
   225  	memberEmbedded := map[string]bool{}
   226  	// TODO(hyangah): define in debug/dwarf package
   227  	const goEmbeddedStruct = dwarf.Attr(0x2903)
   228  	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		switch entry.Tag {
   233  		case dwarf.TagMember:
   234  			name := entry.Val(dwarf.AttrName).(string)
   235  			embedded := entry.Val(goEmbeddedStruct).(bool)
   236  			memberEmbedded[name] = embedded
   237  		case 0:
   238  			return memberEmbedded, nil
   239  		}
   240  	}
   241  	return memberEmbedded, nil
   242  }
   243  
   244  func TestSizes(t *testing.T) {
   245  	if runtime.GOOS == "plan9" {
   246  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   247  	}
   248  
   249  	// DWARF sizes should never be -1.
   250  	// See issue #21097
   251  	const prog = `
   252  package main
   253  var x func()
   254  var y [4]func()
   255  func main() {
   256  	x = nil
   257  	y[0] = nil
   258  }
   259  `
   260  	dir, err := ioutil.TempDir("", "TestSizes")
   261  	if err != nil {
   262  		t.Fatalf("could not create directory: %v", err)
   263  	}
   264  	defer os.RemoveAll(dir)
   265  	f := gobuild(t, dir, prog, NoOpt)
   266  	defer f.Close()
   267  	d, err := f.DWARF()
   268  	if err != nil {
   269  		t.Fatalf("error reading DWARF: %v", err)
   270  	}
   271  	rdr := d.Reader()
   272  	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
   273  		if err != nil {
   274  			t.Fatalf("error reading DWARF: %v", err)
   275  		}
   276  		switch entry.Tag {
   277  		case dwarf.TagArrayType, dwarf.TagPointerType, dwarf.TagStructType, dwarf.TagBaseType, dwarf.TagSubroutineType, dwarf.TagTypedef:
   278  		default:
   279  			continue
   280  		}
   281  		typ, err := d.Type(entry.Offset)
   282  		if err != nil {
   283  			t.Fatalf("can't read type: %v", err)
   284  		}
   285  		if typ.Size() < 0 {
   286  			t.Errorf("subzero size %s %s %T", typ, entry.Tag, typ)
   287  		}
   288  	}
   289  }
   290  
   291  func TestFieldOverlap(t *testing.T) {
   292  	if runtime.GOOS == "plan9" {
   293  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   294  	}
   295  
   296  	// This test grew out of issue 21094, where specific sudog<T> DWARF types
   297  	// had elem fields set to values instead of pointers.
   298  	const prog = `
   299  package main
   300  
   301  var c chan string
   302  
   303  func main() {
   304  	c <- "foo"
   305  }
   306  `
   307  	dir, err := ioutil.TempDir("", "TestFieldOverlap")
   308  	if err != nil {
   309  		t.Fatalf("could not create directory: %v", err)
   310  	}
   311  	defer os.RemoveAll(dir)
   312  
   313  	f := gobuild(t, dir, prog, NoOpt)
   314  	defer f.Close()
   315  
   316  	d, err := f.DWARF()
   317  	if err != nil {
   318  		t.Fatalf("error reading DWARF: %v", err)
   319  	}
   320  
   321  	rdr := d.Reader()
   322  	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
   323  		if err != nil {
   324  			t.Fatalf("error reading DWARF: %v", err)
   325  		}
   326  		if entry.Tag != dwarf.TagStructType {
   327  			continue
   328  		}
   329  		typ, err := d.Type(entry.Offset)
   330  		if err != nil {
   331  			t.Fatalf("can't read type: %v", err)
   332  		}
   333  		s := typ.(*dwarf.StructType)
   334  		for i := 0; i < len(s.Field); i++ {
   335  			end := s.Field[i].ByteOffset + s.Field[i].Type.Size()
   336  			var limit int64
   337  			if i == len(s.Field)-1 {
   338  				limit = s.Size()
   339  			} else {
   340  				limit = s.Field[i+1].ByteOffset
   341  			}
   342  			if end > limit {
   343  				name := entry.Val(dwarf.AttrName).(string)
   344  				t.Fatalf("field %s.%s overlaps next field", name, s.Field[i].Name)
   345  			}
   346  		}
   347  	}
   348  }
   349  
   350  func varDeclCoordsAndSubrogramDeclFile(t *testing.T, testpoint string, expectFile int, expectLine int, directive string) {
   351  
   352  	prog := fmt.Sprintf("package main\n\nfunc main() {\n%s\nvar i int\ni = i\n}\n", directive)
   353  
   354  	dir, err := ioutil.TempDir("", testpoint)
   355  	if err != nil {
   356  		t.Fatalf("could not create directory: %v", err)
   357  	}
   358  	defer os.RemoveAll(dir)
   359  
   360  	f := gobuild(t, dir, prog, NoOpt)
   361  
   362  	d, err := f.DWARF()
   363  	if err != nil {
   364  		t.Fatalf("error reading DWARF: %v", err)
   365  	}
   366  
   367  	rdr := d.Reader()
   368  	ex := examiner{}
   369  	if err := ex.populate(rdr); err != nil {
   370  		t.Fatalf("error reading DWARF: %v", err)
   371  	}
   372  
   373  	// Locate the main.main DIE
   374  	mains := ex.Named("main.main")
   375  	if len(mains) == 0 {
   376  		t.Fatalf("unable to locate DIE for main.main")
   377  	}
   378  	if len(mains) != 1 {
   379  		t.Fatalf("more than one main.main DIE")
   380  	}
   381  	maindie := mains[0]
   382  
   383  	// Vet the main.main DIE
   384  	if maindie.Tag != dwarf.TagSubprogram {
   385  		t.Fatalf("unexpected tag %v on main.main DIE", maindie.Tag)
   386  	}
   387  
   388  	// Walk main's children and select variable "i".
   389  	mainIdx := ex.idxFromOffset(maindie.Offset)
   390  	childDies := ex.Children(mainIdx)
   391  	var iEntry *dwarf.Entry
   392  	for _, child := range childDies {
   393  		if child.Tag == dwarf.TagVariable && child.Val(dwarf.AttrName).(string) == "i" {
   394  			iEntry = child
   395  			break
   396  		}
   397  	}
   398  	if iEntry == nil {
   399  		t.Fatalf("didn't find DW_TAG_variable for i in main.main")
   400  	}
   401  
   402  	// Verify line/file attributes.
   403  	line := iEntry.Val(dwarf.AttrDeclLine)
   404  	if line == nil || line.(int64) != int64(expectLine) {
   405  		t.Errorf("DW_AT_decl_line for i is %v, want %d", line, expectLine)
   406  	}
   407  
   408  	file := maindie.Val(dwarf.AttrDeclFile)
   409  	if file == nil || file.(int64) != 1 {
   410  		t.Errorf("DW_AT_decl_file for main is %v, want %d", file, expectFile)
   411  	}
   412  }
   413  
   414  func TestVarDeclCoordsAndSubrogramDeclFile(t *testing.T) {
   415  	testenv.MustHaveGoBuild(t)
   416  
   417  	if runtime.GOOS == "plan9" {
   418  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   419  	}
   420  
   421  	varDeclCoordsAndSubrogramDeclFile(t, "TestVarDeclCoords", 1, 5, "")
   422  }
   423  
   424  func TestVarDeclCoordsWithLineDirective(t *testing.T) {
   425  	testenv.MustHaveGoBuild(t)
   426  
   427  	if runtime.GOOS == "plan9" {
   428  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   429  	}
   430  
   431  	varDeclCoordsAndSubrogramDeclFile(t, "TestVarDeclCoordsWithLineDirective",
   432  		2, 200, "//line /foobar.go:200")
   433  }
   434  
   435  // Helper class for supporting queries on DIEs within a DWARF .debug_info
   436  // section. Invoke the populate() method below passing in a dwarf.Reader,
   437  // which will read in all DIEs and keep track of parent/child
   438  // relationships. Queries can then be made to ask for DIEs by name or
   439  // by offset. This will hopefully reduce boilerplate for future test
   440  // writing.
   441  
   442  type examiner struct {
   443  	dies        []*dwarf.Entry
   444  	idxByOffset map[dwarf.Offset]int
   445  	kids        map[int][]int
   446  	parent      map[int]int
   447  	byname      map[string][]int
   448  }
   449  
   450  // Populate the examiner using the DIEs read from rdr.
   451  func (ex *examiner) populate(rdr *dwarf.Reader) error {
   452  	ex.idxByOffset = make(map[dwarf.Offset]int)
   453  	ex.kids = make(map[int][]int)
   454  	ex.parent = make(map[int]int)
   455  	ex.byname = make(map[string][]int)
   456  	var nesting []int
   457  	for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
   458  		if err != nil {
   459  			return err
   460  		}
   461  		if entry.Tag == 0 {
   462  			// terminator
   463  			if len(nesting) == 0 {
   464  				return errors.New("nesting stack underflow")
   465  			}
   466  			nesting = nesting[:len(nesting)-1]
   467  			continue
   468  		}
   469  		idx := len(ex.dies)
   470  		ex.dies = append(ex.dies, entry)
   471  		if _, found := ex.idxByOffset[entry.Offset]; found {
   472  			return errors.New("DIE clash on offset")
   473  		}
   474  		ex.idxByOffset[entry.Offset] = idx
   475  		if name, ok := entry.Val(dwarf.AttrName).(string); ok {
   476  			ex.byname[name] = append(ex.byname[name], idx)
   477  		}
   478  		if len(nesting) > 0 {
   479  			parent := nesting[len(nesting)-1]
   480  			ex.kids[parent] = append(ex.kids[parent], idx)
   481  			ex.parent[idx] = parent
   482  		}
   483  		if entry.Children {
   484  			nesting = append(nesting, idx)
   485  		}
   486  	}
   487  	if len(nesting) > 0 {
   488  		return errors.New("unterminated child sequence")
   489  	}
   490  	return nil
   491  }
   492  
   493  func indent(ilevel int) {
   494  	for i := 0; i < ilevel; i++ {
   495  		fmt.Printf("  ")
   496  	}
   497  }
   498  
   499  // For debugging new tests
   500  func (ex *examiner) dumpEntry(idx int, dumpKids bool, ilevel int) error {
   501  	if idx >= len(ex.dies) {
   502  		msg := fmt.Sprintf("bad DIE %d: index out of range\n", idx)
   503  		return errors.New(msg)
   504  	}
   505  	entry := ex.dies[idx]
   506  	indent(ilevel)
   507  	fmt.Printf("0x%x: %v\n", idx, entry.Tag)
   508  	for _, f := range entry.Field {
   509  		indent(ilevel)
   510  		fmt.Printf("at=%v val=0x%x\n", f.Attr, f.Val)
   511  	}
   512  	if dumpKids {
   513  		ksl := ex.kids[idx]
   514  		for _, k := range ksl {
   515  			ex.dumpEntry(k, true, ilevel+2)
   516  		}
   517  	}
   518  	return nil
   519  }
   520  
   521  // Given a DIE offset, return the previously read dwarf.Entry, or nil
   522  func (ex *examiner) entryFromOffset(off dwarf.Offset) *dwarf.Entry {
   523  	if idx, found := ex.idxByOffset[off]; found && idx != -1 {
   524  		return ex.entryFromIdx(idx)
   525  	}
   526  	return nil
   527  }
   528  
   529  // Return the ID that that examiner uses to refer to the DIE at offset off
   530  func (ex *examiner) idxFromOffset(off dwarf.Offset) int {
   531  	if idx, found := ex.idxByOffset[off]; found {
   532  		return idx
   533  	}
   534  	return -1
   535  }
   536  
   537  // Return the dwarf.Entry pointer for the DIE with id 'idx'
   538  func (ex *examiner) entryFromIdx(idx int) *dwarf.Entry {
   539  	if idx >= len(ex.dies) || idx < 0 {
   540  		return nil
   541  	}
   542  	return ex.dies[idx]
   543  }
   544  
   545  // Returns a list of child entries for a die with ID 'idx'
   546  func (ex *examiner) Children(idx int) []*dwarf.Entry {
   547  	sl := ex.kids[idx]
   548  	ret := make([]*dwarf.Entry, len(sl))
   549  	for i, k := range sl {
   550  		ret[i] = ex.entryFromIdx(k)
   551  	}
   552  	return ret
   553  }
   554  
   555  // Returns parent DIE for DIE 'idx', or nil if the DIE is top level
   556  func (ex *examiner) Parent(idx int) *dwarf.Entry {
   557  	p, found := ex.parent[idx]
   558  	if !found {
   559  		return nil
   560  	}
   561  	return ex.entryFromIdx(p)
   562  }
   563  
   564  // Return a list of all DIEs with name 'name'. When searching for DIEs
   565  // by name, keep in mind that the returned results will include child
   566  // DIEs such as params/variables. For example, asking for all DIEs named
   567  // "p" for even a small program will give you 400-500 entries.
   568  func (ex *examiner) Named(name string) []*dwarf.Entry {
   569  	sl := ex.byname[name]
   570  	ret := make([]*dwarf.Entry, len(sl))
   571  	for i, k := range sl {
   572  		ret[i] = ex.entryFromIdx(k)
   573  	}
   574  	return ret
   575  }
   576  
   577  func TestInlinedRoutineRecords(t *testing.T) {
   578  	testenv.MustHaveGoBuild(t)
   579  
   580  	if runtime.GOOS == "plan9" {
   581  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   582  	}
   583  	if runtime.GOOS == "solaris" || runtime.GOOS == "darwin" {
   584  		t.Skip("skipping on solaris and darwin, pending resolution of issue #23168")
   585  	}
   586  
   587  	const prog = `
   588  package main
   589  
   590  var G int
   591  
   592  func noinline(x int) int {
   593  	defer func() { G += x }()
   594  	return x
   595  }
   596  
   597  func cand(x, y int) int {
   598  	return noinline(x+y) ^ (y - x)
   599  }
   600  
   601  func main() {
   602      x := cand(G*G,G|7%G)
   603      G = x
   604  }
   605  `
   606  	dir, err := ioutil.TempDir("", "TestInlinedRoutineRecords")
   607  	if err != nil {
   608  		t.Fatalf("could not create directory: %v", err)
   609  	}
   610  	defer os.RemoveAll(dir)
   611  
   612  	// Note: this is a build with "-l=4", as opposed to "-l -N". The
   613  	// test is intended to verify DWARF that is only generated when
   614  	// the inliner is active. We're only going to look at the DWARF for
   615  	// main.main, however, hence we build with "-gcflags=-l=4" as opposed
   616  	// to "-gcflags=all=-l=4".
   617  	f := gobuild(t, dir, prog, OptInl4)
   618  
   619  	d, err := f.DWARF()
   620  	if err != nil {
   621  		t.Fatalf("error reading DWARF: %v", err)
   622  	}
   623  
   624  	// The inlined subroutines we expect to visit
   625  	expectedInl := []string{"main.cand"}
   626  
   627  	rdr := d.Reader()
   628  	ex := examiner{}
   629  	if err := ex.populate(rdr); err != nil {
   630  		t.Fatalf("error reading DWARF: %v", err)
   631  	}
   632  
   633  	// Locate the main.main DIE
   634  	mains := ex.Named("main.main")
   635  	if len(mains) == 0 {
   636  		t.Fatalf("unable to locate DIE for main.main")
   637  	}
   638  	if len(mains) != 1 {
   639  		t.Fatalf("more than one main.main DIE")
   640  	}
   641  	maindie := mains[0]
   642  
   643  	// Vet the main.main DIE
   644  	if maindie.Tag != dwarf.TagSubprogram {
   645  		t.Fatalf("unexpected tag %v on main.main DIE", maindie.Tag)
   646  	}
   647  
   648  	// Walk main's children and pick out the inlined subroutines
   649  	mainIdx := ex.idxFromOffset(maindie.Offset)
   650  	childDies := ex.Children(mainIdx)
   651  	exCount := 0
   652  	for _, child := range childDies {
   653  		if child.Tag == dwarf.TagInlinedSubroutine {
   654  			// Found an inlined subroutine, locate abstract origin.
   655  			ooff, originOK := child.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
   656  			if !originOK {
   657  				t.Fatalf("no abstract origin attr for inlined subroutine at offset %v", child.Offset)
   658  			}
   659  			originDIE := ex.entryFromOffset(ooff)
   660  			if originDIE == nil {
   661  				t.Fatalf("can't locate origin DIE at off %v", ooff)
   662  			}
   663  
   664  			// Walk the children of the abstract subroutine. We expect
   665  			// to see child variables there, even if (perhaps due to
   666  			// optimization) there are no references to them from the
   667  			// inlined subroutine DIE.
   668  			absFcnIdx := ex.idxFromOffset(ooff)
   669  			absFcnChildDies := ex.Children(absFcnIdx)
   670  			if len(absFcnChildDies) != 2 {
   671  				t.Fatalf("expected abstract function: expected 2 children, got %d children", len(absFcnChildDies))
   672  			}
   673  			formalCount := 0
   674  			for _, absChild := range absFcnChildDies {
   675  				if absChild.Tag == dwarf.TagFormalParameter {
   676  					formalCount += 1
   677  					continue
   678  				}
   679  				t.Fatalf("abstract function child DIE: expected formal, got %v", absChild.Tag)
   680  			}
   681  			if formalCount != 2 {
   682  				t.Fatalf("abstract function DIE: expected 2 formals, got %d", formalCount)
   683  			}
   684  
   685  			if exCount >= len(expectedInl) {
   686  				t.Fatalf("too many inlined subroutines found in main.main")
   687  			}
   688  
   689  			// Name should check out.
   690  			expected := expectedInl[exCount]
   691  			if name, ok := originDIE.Val(dwarf.AttrName).(string); ok {
   692  				if name != expected {
   693  					t.Fatalf("expected inlined routine %s got %s", name, expected)
   694  				}
   695  			}
   696  			exCount++
   697  
   698  			omap := make(map[dwarf.Offset]bool)
   699  
   700  			// Walk the child variables of the inlined routine. Each
   701  			// of them should have a distinct abstract origin-- if two
   702  			// vars point to the same origin things are definitely broken.
   703  			inlIdx := ex.idxFromOffset(child.Offset)
   704  			inlChildDies := ex.Children(inlIdx)
   705  			for _, k := range inlChildDies {
   706  				ooff, originOK := k.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
   707  				if !originOK {
   708  					t.Fatalf("no abstract origin attr for child of inlined subroutine at offset %v", k.Offset)
   709  				}
   710  				if _, found := omap[ooff]; found {
   711  					t.Fatalf("duplicate abstract origin at child of inlined subroutine at offset %v", k.Offset)
   712  				}
   713  				omap[ooff] = true
   714  			}
   715  		}
   716  	}
   717  	if exCount != len(expectedInl) {
   718  		t.Fatalf("not enough inlined subroutines found in main.main")
   719  	}
   720  }
   721  
   722  func abstractOriginSanity(t *testing.T, gopathdir string, flags string) {
   723  
   724  	dir, err := ioutil.TempDir("", "TestAbstractOriginSanity")
   725  	if err != nil {
   726  		t.Fatalf("could not create directory: %v", err)
   727  	}
   728  	defer os.RemoveAll(dir)
   729  
   730  	// Build with inlining, to exercise DWARF inlining support.
   731  	f := gobuildTestdata(t, dir, gopathdir, "main", flags)
   732  
   733  	d, err := f.DWARF()
   734  	if err != nil {
   735  		t.Fatalf("error reading DWARF: %v", err)
   736  	}
   737  	rdr := d.Reader()
   738  	ex := examiner{}
   739  	if err := ex.populate(rdr); err != nil {
   740  		t.Fatalf("error reading DWARF: %v", err)
   741  	}
   742  
   743  	// Make a pass through all DIEs looking for abstract origin
   744  	// references.
   745  	abscount := 0
   746  	for i, die := range ex.dies {
   747  		// Does it have an abstract origin?
   748  		ooff, originOK := die.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
   749  		if !originOK {
   750  			continue
   751  		}
   752  
   753  		// All abstract origin references should be resolvable.
   754  		abscount += 1
   755  		originDIE := ex.entryFromOffset(ooff)
   756  		if originDIE == nil {
   757  			ex.dumpEntry(i, false, 0)
   758  			t.Fatalf("unresolved abstract origin ref in DIE at offset 0x%x\n", die.Offset)
   759  		}
   760  
   761  		// Suppose that DIE X has parameter/variable children {K1,
   762  		// K2, ... KN}. If X has an abstract origin of A, then for
   763  		// each KJ, the abstract origin of KJ should be a child of A.
   764  		// Note that this same rule doesn't hold for non-variable DIEs.
   765  		pidx := ex.idxFromOffset(die.Offset)
   766  		if pidx < 0 {
   767  			t.Fatalf("can't locate DIE id")
   768  		}
   769  		kids := ex.Children(pidx)
   770  		for _, kid := range kids {
   771  			if kid.Tag != dwarf.TagVariable &&
   772  				kid.Tag != dwarf.TagFormalParameter {
   773  				continue
   774  			}
   775  			kooff, originOK := kid.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
   776  			if !originOK {
   777  				continue
   778  			}
   779  			childOriginDIE := ex.entryFromOffset(kooff)
   780  			if childOriginDIE == nil {
   781  				ex.dumpEntry(i, false, 0)
   782  				t.Fatalf("unresolved abstract origin ref in DIE at offset %x", kid.Offset)
   783  			}
   784  			coidx := ex.idxFromOffset(childOriginDIE.Offset)
   785  			childOriginParent := ex.Parent(coidx)
   786  			if childOriginParent != originDIE {
   787  				ex.dumpEntry(i, false, 0)
   788  				t.Fatalf("unexpected parent of abstract origin DIE at offset %v", childOriginDIE.Offset)
   789  			}
   790  		}
   791  	}
   792  	if abscount == 0 {
   793  		t.Fatalf("no abstract origin refs found, something is wrong")
   794  	}
   795  }
   796  
   797  func TestAbstractOriginSanity(t *testing.T) {
   798  	testenv.MustHaveGoBuild(t)
   799  
   800  	if testing.Short() {
   801  		t.Skip("skipping test in short mode.")
   802  	}
   803  
   804  	if runtime.GOOS == "plan9" {
   805  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   806  	}
   807  	if runtime.GOOS == "solaris" || runtime.GOOS == "darwin" {
   808  		t.Skip("skipping on solaris and darwin, pending resolution of issue #23168")
   809  	}
   810  
   811  	if wd, err := os.Getwd(); err == nil {
   812  		gopathdir := filepath.Join(wd, "testdata", "httptest")
   813  		abstractOriginSanity(t, gopathdir, OptAllInl4)
   814  	} else {
   815  		t.Fatalf("os.Getwd() failed %v", err)
   816  	}
   817  }
   818  
   819  func TestAbstractOriginSanityIssue25459(t *testing.T) {
   820  	testenv.MustHaveGoBuild(t)
   821  
   822  	if runtime.GOOS == "plan9" {
   823  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   824  	}
   825  	if runtime.GOOS == "solaris" || runtime.GOOS == "darwin" {
   826  		t.Skip("skipping on solaris and darwin, pending resolution of issue #23168")
   827  	}
   828  	if runtime.GOARCH != "amd64" && runtime.GOARCH != "x86" {
   829  		t.Skip("skipping on not-amd64 not-x86; location lists not supported")
   830  	}
   831  
   832  	if wd, err := os.Getwd(); err == nil {
   833  		gopathdir := filepath.Join(wd, "testdata", "issue25459")
   834  		abstractOriginSanity(t, gopathdir, DefaultOpt)
   835  	} else {
   836  		t.Fatalf("os.Getwd() failed %v", err)
   837  	}
   838  }
   839  
   840  func TestAbstractOriginSanityIssue26237(t *testing.T) {
   841  	testenv.MustHaveGoBuild(t)
   842  
   843  	if runtime.GOOS == "plan9" {
   844  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   845  	}
   846  	if runtime.GOOS == "solaris" || runtime.GOOS == "darwin" {
   847  		t.Skip("skipping on solaris and darwin, pending resolution of issue #23168")
   848  	}
   849  	if wd, err := os.Getwd(); err == nil {
   850  		gopathdir := filepath.Join(wd, "testdata", "issue26237")
   851  		abstractOriginSanity(t, gopathdir, DefaultOpt)
   852  	} else {
   853  		t.Fatalf("os.Getwd() failed %v", err)
   854  	}
   855  }
   856  
   857  func TestRuntimeTypeAttrInternal(t *testing.T) {
   858  	testenv.MustHaveGoBuild(t)
   859  
   860  	if runtime.GOOS == "plan9" {
   861  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   862  	}
   863  
   864  	testRuntimeTypeAttr(t, "-ldflags=-linkmode=internal")
   865  }
   866  
   867  // External linking requires a host linker (https://golang.org/src/cmd/cgo/doc.go l.732)
   868  func TestRuntimeTypeAttrExternal(t *testing.T) {
   869  	testenv.MustHaveGoBuild(t)
   870  	testenv.MustHaveCGO(t)
   871  
   872  	if runtime.GOOS == "plan9" {
   873  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   874  	}
   875  
   876  	// Explicitly test external linking, for dsymutil compatibility on Darwin.
   877  	if runtime.GOARCH == "ppc64" {
   878  		t.Skip("-linkmode=external not supported on ppc64")
   879  	}
   880  	testRuntimeTypeAttr(t, "-ldflags=-linkmode=external")
   881  }
   882  
   883  func testRuntimeTypeAttr(t *testing.T, flags string) {
   884  	const prog = `
   885  package main
   886  
   887  import "unsafe"
   888  
   889  type X struct{ _ int }
   890  
   891  func main() {
   892  	var x interface{} = &X{}
   893  	p := *(*uintptr)(unsafe.Pointer(&x))
   894  	print(p)
   895  }
   896  `
   897  	dir, err := ioutil.TempDir("", "TestRuntimeType")
   898  	if err != nil {
   899  		t.Fatalf("could not create directory: %v", err)
   900  	}
   901  	defer os.RemoveAll(dir)
   902  
   903  	f := gobuild(t, dir, prog, flags)
   904  	out, err := exec.Command(f.path).CombinedOutput()
   905  	if err != nil {
   906  		t.Fatalf("could not run test program: %v", err)
   907  	}
   908  	addr, err := strconv.ParseUint(string(out), 10, 64)
   909  	if err != nil {
   910  		t.Fatalf("could not parse type address from program output %q: %v", out, err)
   911  	}
   912  
   913  	symbols, err := f.Symbols()
   914  	if err != nil {
   915  		t.Fatalf("error reading symbols: %v", err)
   916  	}
   917  	var types *objfilepkg.Sym
   918  	for _, sym := range symbols {
   919  		if sym.Name == "runtime.types" {
   920  			types = &sym
   921  			break
   922  		}
   923  	}
   924  	if types == nil {
   925  		t.Fatal("couldn't find runtime.types in symbols")
   926  	}
   927  
   928  	d, err := f.DWARF()
   929  	if err != nil {
   930  		t.Fatalf("error reading DWARF: %v", err)
   931  	}
   932  
   933  	rdr := d.Reader()
   934  	ex := examiner{}
   935  	if err := ex.populate(rdr); err != nil {
   936  		t.Fatalf("error reading DWARF: %v", err)
   937  	}
   938  	dies := ex.Named("*main.X")
   939  	if len(dies) != 1 {
   940  		t.Fatalf("wanted 1 DIE named *main.X, found %v", len(dies))
   941  	}
   942  	rtAttr := dies[0].Val(0x2904)
   943  	if rtAttr == nil {
   944  		t.Fatalf("*main.X DIE had no runtime type attr. DIE: %v", dies[0])
   945  	}
   946  
   947  	if rtAttr.(uint64)+types.Addr != addr {
   948  		t.Errorf("DWARF type offset was %#x+%#x, but test program said %#x", rtAttr.(uint64), types.Addr, addr)
   949  	}
   950  }
   951  

View as plain text