// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bytes" "debug/goobj" "fmt" "math/rand" "sort" "strings" "testing" ) // Test of pcln table encoding. // testdata/genpcln.go generates an assembly file with // pseudorandom values for the data that pclntab stores. // This test recomputes the same pseudorandom stream // and checks that the final linked binary uses those values // as well. func TestPclntab(t *testing.T) { p := &Prog{ GOOS: "darwin", GOARCH: "amd64", Error: func(s string) { t.Error(s) }, StartSym: "start", omitRuntime: true, } var buf bytes.Buffer p.link(&buf, "testdata/pclntab.6") if p.NumError > 0 { return } // The algorithm for computing values here must match // the one in testdata/genpcln.go. for f := 0; f < 3; f++ { file := "input" line := 1 rnd := rand.New(rand.NewSource(int64(f))) args := rnd.Intn(100) * 8 frame := 32 + rnd.Intn(32)/8*8 size := 200 + rnd.Intn(100)*8 name := fmt.Sprintf("func%d", f) r, off, fargs, fframe, ok := findFunc(t, p, name) if !ok { continue // error already printed } if fargs != args { t.Errorf("%s: args=%d, want %d", name, fargs, args) } if fframe != frame+8 { t.Errorf("%s: frame=%d, want %d", name, fframe, frame+8) } // Check FUNCDATA 1. fdata, ok := loadFuncdata(t, r, name, off, 1) if ok { fsym := p.Syms[goobj.SymID{Name: fmt.Sprintf("funcdata%d", f)}] if fsym == nil { t.Errorf("funcdata%d is missing in binary", f) } else if fdata != fsym.Addr { t.Errorf("%s: funcdata 1 = %#x, want %#x", name, fdata, fsym.Addr) } } // Walk code checking pcdata values. spadj := 0 pcdata1 := -1 pcdata2 := -1 checkPCSP(t, r, name, off, 0, 0) checkPCData(t, r, name, off, 0, 0, -1) checkPCData(t, r, name, off, 0, 1, -1) checkPCData(t, r, name, off, 0, 2, -1) firstpc := 4 for i := 0; i < size; i++ { pc := firstpc + i // skip SP adjustment to allocate frame if i >= 0x100 && t.Failed() { break } // Possible SP adjustment. checkPCSP(t, r, name, off, pc, frame+spadj) if rnd.Intn(100) == 0 { checkPCFileLine(t, r, name, off, pc, file, line) checkPCData(t, r, name, off, pc, 1, pcdata1) checkPCData(t, r, name, off, pc, 2, pcdata2) i += 1 pc = firstpc + i checkPCFileLine(t, r, name, off, pc-1, file, line) checkPCData(t, r, name, off, pc-1, 1, pcdata1) checkPCData(t, r, name, off, pc-1, 2, pcdata2) checkPCSP(t, r, name, off, pc-1, frame+spadj) if spadj <= -32 || spadj < 32 && rnd.Intn(2) == 0 { spadj += 8 } else { spadj -= 8 } checkPCSP(t, r, name, off, pc, frame+spadj) } // Possible PCFile change. if rnd.Intn(100) == 0 { file = fmt.Sprintf("file%d.s", rnd.Intn(10)) line = rnd.Intn(100) + 1 } // Possible PCLine change. if rnd.Intn(10) == 0 { line = rnd.Intn(1000) + 1 } // Possible PCData $1 change. if rnd.Intn(100) == 0 { pcdata1 = rnd.Intn(1000) } // Possible PCData $2 change. if rnd.Intn(100) == 0 { pcdata2 = rnd.Intn(1000) } if i == 0 { checkPCFileLine(t, r, name, off, 0, file, line) checkPCFileLine(t, r, name, off, pc-1, file, line) } checkPCFileLine(t, r, name, off, pc, file, line) checkPCData(t, r, name, off, pc, 1, pcdata1) checkPCData(t, r, name, off, pc, 2, pcdata2) } } } // findFunc finds the function information in the pclntab of p // for the function with the given name. // It returns a symbol reader for pclntab, the offset of the function information // within that symbol, and the args and frame values read out of the information. func findFunc(t *testing.T, p *Prog, name string) (r *SymReader, off, args, frame int, ok bool) { tabsym := p.Syms[goobj.SymID{Name: "pclntab"}] if tabsym == nil { t.Errorf("pclntab is missing in binary") return } r = new(SymReader) r.Init(p, tabsym) // pclntab must with 8-byte header 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) { t.Errorf("pclntab has incorrect header %.8x", r.data[:8]) return } sym := p.Syms[goobj.SymID{Name: name}] if sym == nil { t.Errorf("%s is missing in the binary", name) return } // index is nfunc addr0 off0 addr1 off1 ... addr_nfunc (sentinel) nfunc := int(r.Addr(8)) i := sort.Search(nfunc, func(i int) bool { return r.Addr(8+p.ptrsize*(1+2*i)) >= sym.Addr }) if entry := r.Addr(8 + p.ptrsize*(1+2*i)); entry != sym.Addr { indexTab := make([]Addr, 2*nfunc+1) for j := range indexTab { indexTab[j] = r.Addr(8 + p.ptrsize*(1+j)) } t.Errorf("pclntab is missing entry for %s (%#x): %#x", name, sym.Addr, indexTab) return } off = int(r.Addr(8 + p.ptrsize*(1+2*i+1))) // func description at off is // entry addr // nameoff uint32 // args uint32 // frame uint32 // pcspoff uint32 // pcfileoff uint32 // pclineoff uint32 // npcdata uint32 // nfuncdata uint32 // pcdata npcdata*uint32 // funcdata nfuncdata*addr // if entry := r.Addr(off); entry != sym.Addr { t.Errorf("pclntab inconsistent: entry for %s addr=%#x has entry=%#x", name, sym.Addr, entry) return } nameoff := int(r.Uint32(off + p.ptrsize)) args = int(r.Uint32(off + p.ptrsize + 1*4)) frame = int(r.Uint32(off + p.ptrsize + 2*4)) fname := r.String(nameoff) if fname != name { t.Errorf("pclntab inconsistent: entry for %s addr=%#x has name %q", name, sym.Addr, fname) } ok = true // off, args, frame are usable return } // loadFuncdata returns the funcdata #fnum value // loaded from the function information for name. func loadFuncdata(t *testing.T, r *SymReader, name string, off int, fnum int) (Addr, bool) { npcdata := int(r.Uint32(off + r.p.ptrsize + 6*4)) nfuncdata := int(r.Uint32(off + r.p.ptrsize + 7*4)) if fnum >= nfuncdata { t.Errorf("pclntab(%s): no funcdata %d (only < %d)", name, fnum, nfuncdata) return 0, false } fdataoff := off + r.p.ptrsize + (8+npcdata)*4 + fnum*r.p.ptrsize fdataoff += fdataoff & 4 return r.Addr(fdataoff), true } // checkPCSP checks that the PCSP table in the function information at off // lists spadj as the sp delta for pc. func checkPCSP(t *testing.T, r *SymReader, name string, off, pc, spadj int) { pcoff := r.Uint32(off + r.p.ptrsize + 3*4) pcval, ok := readPCData(t, r, name, "PCSP", pcoff, pc) if !ok { return } if pcval != spadj { t.Errorf("pclntab(%s): at pc=+%#x, pcsp=%d, want %d", name, pc, pcval, spadj) } } // checkPCSP checks that the PCFile and PCLine tables in the function information at off // list file, line as the file name and line number for pc. func checkPCFileLine(t *testing.T, r *SymReader, name string, off, pc int, file string, line int) { pcfileoff := r.Uint32(off + r.p.ptrsize + 4*4) pclineoff := r.Uint32(off + r.p.ptrsize + 5*4) pcfilenum, ok1 := readPCData(t, r, name, "PCFile", pcfileoff, pc) pcline, ok2 := readPCData(t, r, name, "PCLine", pclineoff, pc) if !ok1 || !ok2 { return } nfunc := int(r.Addr(8)) filetaboff := r.Uint32(8 + r.p.ptrsize*2*(nfunc+1)) nfile := int(r.Uint32(int(filetaboff))) if pcfilenum <= 0 || pcfilenum >= nfile { t.Errorf("pclntab(%s): at pc=+%#x, filenum=%d (invalid; nfile=%d)", name, pc, pcfilenum, nfile) } pcfile := r.String(int(r.Uint32(int(filetaboff) + pcfilenum*4))) if !strings.HasSuffix(pcfile, file) { t.Errorf("pclntab(%s): at pc=+%#x, file=%q, want %q", name, pc, pcfile, file) } if pcline != line { t.Errorf("pclntab(%s): at pc=+%#x, line=%d, want %d", name, pc, pcline, line) } } // checkPCData checks that the PCData#pnum table in the function information at off // list val as the value for pc. func checkPCData(t *testing.T, r *SymReader, name string, off, pc, pnum, val int) { pcoff := r.Uint32(off + r.p.ptrsize + (8+pnum)*4) pcval, ok := readPCData(t, r, name, fmt.Sprintf("PCData#%d", pnum), pcoff, pc) if !ok { return } if pcval != val { t.Errorf("pclntab(%s): at pc=+%#x, pcdata#%d=%d, want %d", name, pc, pnum, pcval, val) } } // readPCData reads the PCData table offset off // to obtain and return the value associated with pc. func readPCData(t *testing.T, r *SymReader, name, pcdataname string, pcoff uint32, pc int) (int, bool) { // "If pcsp, pcfile, pcln, or any of the pcdata offsets is zero, // that table is considered missing, and all PCs take value -1." if pcoff == 0 { return -1, true } var it PCIter for it.Init(r.p, r.data[pcoff:]); !it.Done; it.Next() { if it.PC <= uint32(pc) && uint32(pc) < it.NextPC { return int(it.Value), true } } if it.Corrupt { t.Errorf("pclntab(%s): %s: corrupt pcdata table", name, pcdataname) } return 0, false } // A SymReader provides typed access to the data for a symbol. type SymReader struct { p *Prog data []byte } func (r *SymReader) Init(p *Prog, sym *Sym) { seg := sym.Section.Segment off := sym.Addr - seg.VirtAddr data := seg.Data[off : off+Addr(sym.Size)] r.p = p r.data = data } func (r *SymReader) Uint8(off int) uint8 { return r.data[off] } func (r *SymReader) Uint16(off int) uint16 { return r.p.byteorder.Uint16(r.data[off:]) } func (r *SymReader) Uint32(off int) uint32 { return r.p.byteorder.Uint32(r.data[off:]) } func (r *SymReader) Uint64(off int) uint64 { return r.p.byteorder.Uint64(r.data[off:]) } func (r *SymReader) Addr(off int) Addr { if r.p.ptrsize == 4 { return Addr(r.Uint32(off)) } return Addr(r.Uint64(off)) } func (r *SymReader) String(off int) string { end := off for r.data[end] != '\x00' { end++ } return string(r.data[off:end]) }