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 }