github.com/yanyiwu/go@v0.0.0-20150106053140-03d6637dbb7f/src/cmd/link/pclntab_test.go (about)

     1  // Copyright 2014 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  	"cmd/internal/goobj"
    10  	"fmt"
    11  	"math/rand"
    12  	"sort"
    13  	"strings"
    14  	"testing"
    15  )
    16  
    17  // Test of pcln table encoding.
    18  // testdata/genpcln.go generates an assembly file with
    19  // pseudorandom values for the data that pclntab stores.
    20  // This test recomputes the same pseudorandom stream
    21  // and checks that the final linked binary uses those values
    22  // as well.
    23  func TestPclntab(t *testing.T) {
    24  	p := &Prog{
    25  		GOOS:        "darwin",
    26  		GOARCH:      "amd64",
    27  		Error:       func(s string) { t.Error(s) },
    28  		StartSym:    "start",
    29  		omitRuntime: true,
    30  	}
    31  	var buf bytes.Buffer
    32  	p.link(&buf, "testdata/pclntab.6")
    33  	if p.NumError > 0 {
    34  		return
    35  	}
    36  
    37  	// The algorithm for computing values here must match
    38  	// the one in testdata/genpcln.go.
    39  	for f := 0; f < 3; f++ {
    40  		file := "input"
    41  		line := 1
    42  		rnd := rand.New(rand.NewSource(int64(f)))
    43  		args := rnd.Intn(100) * 8
    44  		frame := 32 + rnd.Intn(32)/8*8
    45  		size := 200 + rnd.Intn(100)*8
    46  
    47  		name := fmt.Sprintf("func%d", f)
    48  		r, off, fargs, fframe, ok := findFunc(t, p, name)
    49  		if !ok {
    50  			continue // error already printed
    51  		}
    52  		if fargs != args {
    53  			t.Errorf("%s: args=%d, want %d", name, fargs, args)
    54  		}
    55  		if fframe != frame+8 {
    56  			t.Errorf("%s: frame=%d, want %d", name, fframe, frame+8)
    57  		}
    58  
    59  		// Check FUNCDATA 1.
    60  		fdata, ok := loadFuncdata(t, r, name, off, 1)
    61  		if ok {
    62  			fsym := p.Syms[goobj.SymID{Name: fmt.Sprintf("funcdata%d", f)}]
    63  			if fsym == nil {
    64  				t.Errorf("funcdata%d is missing in binary", f)
    65  			} else if fdata != fsym.Addr {
    66  				t.Errorf("%s: funcdata 1 = %#x, want %#x", name, fdata, fsym.Addr)
    67  			}
    68  		}
    69  
    70  		// Walk code checking pcdata values.
    71  		spadj := 0
    72  		pcdata1 := -1
    73  		pcdata2 := -1
    74  
    75  		checkPCSP(t, r, name, off, 0, 0)
    76  		checkPCData(t, r, name, off, 0, 0, -1)
    77  		checkPCData(t, r, name, off, 0, 1, -1)
    78  		checkPCData(t, r, name, off, 0, 2, -1)
    79  
    80  		firstpc := 4
    81  		for i := 0; i < size; i++ {
    82  			pc := firstpc + i // skip SP adjustment to allocate frame
    83  			if i >= 0x100 && t.Failed() {
    84  				break
    85  			}
    86  			// Possible SP adjustment.
    87  			checkPCSP(t, r, name, off, pc, frame+spadj)
    88  			if rnd.Intn(100) == 0 {
    89  				checkPCFileLine(t, r, name, off, pc, file, line)
    90  				checkPCData(t, r, name, off, pc, 1, pcdata1)
    91  				checkPCData(t, r, name, off, pc, 2, pcdata2)
    92  				i += 1
    93  				pc = firstpc + i
    94  				checkPCFileLine(t, r, name, off, pc-1, file, line)
    95  				checkPCData(t, r, name, off, pc-1, 1, pcdata1)
    96  				checkPCData(t, r, name, off, pc-1, 2, pcdata2)
    97  				checkPCSP(t, r, name, off, pc-1, frame+spadj)
    98  
    99  				if spadj <= -32 || spadj < 32 && rnd.Intn(2) == 0 {
   100  					spadj += 8
   101  				} else {
   102  					spadj -= 8
   103  				}
   104  				checkPCSP(t, r, name, off, pc, frame+spadj)
   105  			}
   106  
   107  			// Possible PCFile change.
   108  			if rnd.Intn(100) == 0 {
   109  				file = fmt.Sprintf("file%d.s", rnd.Intn(10))
   110  				line = rnd.Intn(100) + 1
   111  			}
   112  
   113  			// Possible PCLine change.
   114  			if rnd.Intn(10) == 0 {
   115  				line = rnd.Intn(1000) + 1
   116  			}
   117  
   118  			// Possible PCData $1 change.
   119  			if rnd.Intn(100) == 0 {
   120  				pcdata1 = rnd.Intn(1000)
   121  			}
   122  
   123  			// Possible PCData $2 change.
   124  			if rnd.Intn(100) == 0 {
   125  				pcdata2 = rnd.Intn(1000)
   126  			}
   127  
   128  			if i == 0 {
   129  				checkPCFileLine(t, r, name, off, 0, file, line)
   130  				checkPCFileLine(t, r, name, off, pc-1, file, line)
   131  			}
   132  			checkPCFileLine(t, r, name, off, pc, file, line)
   133  			checkPCData(t, r, name, off, pc, 1, pcdata1)
   134  			checkPCData(t, r, name, off, pc, 2, pcdata2)
   135  		}
   136  	}
   137  }
   138  
   139  // findFunc finds the function information in the pclntab of p
   140  // for the function with the given name.
   141  // It returns a symbol reader for pclntab, the offset of the function information
   142  // within that symbol, and the args and frame values read out of the information.
   143  func findFunc(t *testing.T, p *Prog, name string) (r *SymReader, off, args, frame int, ok bool) {
   144  	tabsym := p.Syms[goobj.SymID{Name: "runtime.pclntab"}]
   145  	if tabsym == nil {
   146  		t.Errorf("pclntab is missing in binary")
   147  		return
   148  	}
   149  
   150  	r = new(SymReader)
   151  	r.Init(p, tabsym)
   152  
   153  	// pclntab must with 8-byte header
   154  	if r.Uint32(0) != 0xfffffffb || r.Uint8(4) != 0 || r.Uint8(5) != 0 || r.Uint8(6) != uint8(p.pcquantum) || r.Uint8(7) != uint8(p.ptrsize) {
   155  		t.Errorf("pclntab has incorrect header %.8x", r.data[:8])
   156  		return
   157  	}
   158  
   159  	sym := p.Syms[goobj.SymID{Name: name}]
   160  	if sym == nil {
   161  		t.Errorf("%s is missing in the binary", name)
   162  		return
   163  	}
   164  
   165  	// index is nfunc addr0 off0 addr1 off1 ... addr_nfunc (sentinel)
   166  	nfunc := int(r.Addr(8))
   167  	i := sort.Search(nfunc, func(i int) bool {
   168  		return r.Addr(8+p.ptrsize*(1+2*i)) >= sym.Addr
   169  	})
   170  	if entry := r.Addr(8 + p.ptrsize*(1+2*i)); entry != sym.Addr {
   171  		indexTab := make([]Addr, 2*nfunc+1)
   172  		for j := range indexTab {
   173  			indexTab[j] = r.Addr(8 + p.ptrsize*(1+j))
   174  		}
   175  		t.Errorf("pclntab is missing entry for %s (%#x): %#x", name, sym.Addr, indexTab)
   176  		return
   177  	}
   178  
   179  	off = int(r.Addr(8 + p.ptrsize*(1+2*i+1)))
   180  
   181  	// func description at off is
   182  	//	entry addr
   183  	//	nameoff uint32
   184  	//	args uint32
   185  	//	frame uint32
   186  	//	pcspoff uint32
   187  	//	pcfileoff uint32
   188  	//	pclineoff uint32
   189  	//	npcdata uint32
   190  	//	nfuncdata uint32
   191  	//	pcdata npcdata*uint32
   192  	//	funcdata nfuncdata*addr
   193  	//
   194  	if entry := r.Addr(off); entry != sym.Addr {
   195  		t.Errorf("pclntab inconsistent: entry for %s addr=%#x has entry=%#x", name, sym.Addr, entry)
   196  		return
   197  	}
   198  	nameoff := int(r.Uint32(off + p.ptrsize))
   199  	args = int(r.Uint32(off + p.ptrsize + 1*4))
   200  	frame = int(r.Uint32(off + p.ptrsize + 2*4))
   201  
   202  	fname := r.String(nameoff)
   203  	if fname != name {
   204  		t.Errorf("pclntab inconsistent: entry for %s addr=%#x has name %q", name, sym.Addr, fname)
   205  	}
   206  
   207  	ok = true // off, args, frame are usable
   208  	return
   209  }
   210  
   211  // loadFuncdata returns the funcdata #fnum value
   212  // loaded from the function information for name.
   213  func loadFuncdata(t *testing.T, r *SymReader, name string, off int, fnum int) (Addr, bool) {
   214  	npcdata := int(r.Uint32(off + r.p.ptrsize + 6*4))
   215  	nfuncdata := int(r.Uint32(off + r.p.ptrsize + 7*4))
   216  	if fnum >= nfuncdata {
   217  		t.Errorf("pclntab(%s): no funcdata %d (only < %d)", name, fnum, nfuncdata)
   218  		return 0, false
   219  	}
   220  	fdataoff := off + r.p.ptrsize + (8+npcdata)*4 + fnum*r.p.ptrsize
   221  	fdataoff += fdataoff & 4
   222  	return r.Addr(fdataoff), true
   223  }
   224  
   225  // checkPCSP checks that the PCSP table in the function information at off
   226  // lists spadj as the sp delta for pc.
   227  func checkPCSP(t *testing.T, r *SymReader, name string, off, pc, spadj int) {
   228  	pcoff := r.Uint32(off + r.p.ptrsize + 3*4)
   229  	pcval, ok := readPCData(t, r, name, "PCSP", pcoff, pc)
   230  	if !ok {
   231  		return
   232  	}
   233  	if pcval != spadj {
   234  		t.Errorf("pclntab(%s): at pc=+%#x, pcsp=%d, want %d", name, pc, pcval, spadj)
   235  	}
   236  }
   237  
   238  // checkPCSP checks that the PCFile and PCLine tables in the function information at off
   239  // list file, line as the file name and line number for pc.
   240  func checkPCFileLine(t *testing.T, r *SymReader, name string, off, pc int, file string, line int) {
   241  	pcfileoff := r.Uint32(off + r.p.ptrsize + 4*4)
   242  	pclineoff := r.Uint32(off + r.p.ptrsize + 5*4)
   243  	pcfilenum, ok1 := readPCData(t, r, name, "PCFile", pcfileoff, pc)
   244  	pcline, ok2 := readPCData(t, r, name, "PCLine", pclineoff, pc)
   245  	if !ok1 || !ok2 {
   246  		return
   247  	}
   248  	nfunc := int(r.Addr(8))
   249  	filetaboff := r.Uint32(8 + r.p.ptrsize*2*(nfunc+1))
   250  	nfile := int(r.Uint32(int(filetaboff)))
   251  	if pcfilenum <= 0 || pcfilenum >= nfile {
   252  		t.Errorf("pclntab(%s): at pc=+%#x, filenum=%d (invalid; nfile=%d)", name, pc, pcfilenum, nfile)
   253  	}
   254  	pcfile := r.String(int(r.Uint32(int(filetaboff) + pcfilenum*4)))
   255  	if !strings.HasSuffix(pcfile, file) {
   256  		t.Errorf("pclntab(%s): at pc=+%#x, file=%q, want %q", name, pc, pcfile, file)
   257  	}
   258  	if pcline != line {
   259  		t.Errorf("pclntab(%s): at pc=+%#x, line=%d, want %d", name, pc, pcline, line)
   260  	}
   261  }
   262  
   263  // checkPCData checks that the PCData#pnum table in the function information at off
   264  // list val as the value for pc.
   265  func checkPCData(t *testing.T, r *SymReader, name string, off, pc, pnum, val int) {
   266  	pcoff := r.Uint32(off + r.p.ptrsize + (8+pnum)*4)
   267  	pcval, ok := readPCData(t, r, name, fmt.Sprintf("PCData#%d", pnum), pcoff, pc)
   268  	if !ok {
   269  		return
   270  	}
   271  	if pcval != val {
   272  		t.Errorf("pclntab(%s): at pc=+%#x, pcdata#%d=%d, want %d", name, pc, pnum, pcval, val)
   273  	}
   274  }
   275  
   276  // readPCData reads the PCData table offset off
   277  // to obtain and return the value associated with pc.
   278  func readPCData(t *testing.T, r *SymReader, name, pcdataname string, pcoff uint32, pc int) (int, bool) {
   279  	// "If pcsp, pcfile, pcln, or any of the pcdata offsets is zero,
   280  	// that table is considered missing, and all PCs take value -1."
   281  	if pcoff == 0 {
   282  		return -1, true
   283  	}
   284  
   285  	var it PCIter
   286  	for it.Init(r.p, r.data[pcoff:]); !it.Done; it.Next() {
   287  		if it.PC <= uint32(pc) && uint32(pc) < it.NextPC {
   288  			return int(it.Value), true
   289  		}
   290  	}
   291  	if it.Corrupt {
   292  		t.Errorf("pclntab(%s): %s: corrupt pcdata table", name, pcdataname)
   293  	}
   294  	return 0, false
   295  }
   296  
   297  // A SymReader provides typed access to the data for a symbol.
   298  type SymReader struct {
   299  	p    *Prog
   300  	data []byte
   301  }
   302  
   303  func (r *SymReader) Init(p *Prog, sym *Sym) {
   304  	seg := sym.Section.Segment
   305  	off := sym.Addr - seg.VirtAddr
   306  	data := seg.Data[off : off+Addr(sym.Size)]
   307  	r.p = p
   308  	r.data = data
   309  }
   310  
   311  func (r *SymReader) Uint8(off int) uint8 {
   312  	return r.data[off]
   313  }
   314  
   315  func (r *SymReader) Uint16(off int) uint16 {
   316  	return r.p.byteorder.Uint16(r.data[off:])
   317  }
   318  
   319  func (r *SymReader) Uint32(off int) uint32 {
   320  	return r.p.byteorder.Uint32(r.data[off:])
   321  }
   322  
   323  func (r *SymReader) Uint64(off int) uint64 {
   324  	return r.p.byteorder.Uint64(r.data[off:])
   325  }
   326  
   327  func (r *SymReader) Addr(off int) Addr {
   328  	if r.p.ptrsize == 4 {
   329  		return Addr(r.Uint32(off))
   330  	}
   331  	return Addr(r.Uint64(off))
   332  }
   333  
   334  func (r *SymReader) String(off int) string {
   335  	end := off
   336  	for r.data[end] != '\x00' {
   337  		end++
   338  	}
   339  	return string(r.data[off:end])
   340  }