LEFT | RIGHT |
(no file at all) | |
| 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 "debug/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 fram
e |
| 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, lin
e) |
| 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, lin
e) |
| 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 informatio
n |
| 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, fram
e int, ok bool) { |
| 144 tabsym := p.Syms[goobj.SymID{Name: "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) (A
ddr, 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 informatio
n 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), pcof
f, 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 uint3
2, pc int) (int, bool) { |
| 279 var it PCIter |
| 280 for it.Init(r.p, r.data[pcoff:]); !it.Done; it.Next() { |
| 281 if it.PC <= uint32(pc) && uint32(pc) < it.NextPC { |
| 282 return int(it.Value), true |
| 283 } |
| 284 } |
| 285 if it.Corrupt { |
| 286 t.Errorf("pclntab(%s): %s: corrupt pcdata table", name, pcdatana
me) |
| 287 } |
| 288 return 0, false |
| 289 } |
| 290 |
| 291 // A SymReader provides typed access to the data for a symbol. |
| 292 type SymReader struct { |
| 293 p *Prog |
| 294 data []byte |
| 295 } |
| 296 |
| 297 func (r *SymReader) Init(p *Prog, sym *Sym) { |
| 298 seg := sym.Section.Segment |
| 299 off := sym.Addr - seg.VirtAddr |
| 300 data := seg.Data[off : off+Addr(sym.Size)] |
| 301 r.p = p |
| 302 r.data = data |
| 303 } |
| 304 |
| 305 func (r *SymReader) Uint8(off int) uint8 { |
| 306 return r.data[off] |
| 307 } |
| 308 |
| 309 func (r *SymReader) Uint16(off int) uint16 { |
| 310 return r.p.byteorder.Uint16(r.data[off:]) |
| 311 } |
| 312 |
| 313 func (r *SymReader) Uint32(off int) uint32 { |
| 314 return r.p.byteorder.Uint32(r.data[off:]) |
| 315 } |
| 316 |
| 317 func (r *SymReader) Uint64(off int) uint64 { |
| 318 return r.p.byteorder.Uint64(r.data[off:]) |
| 319 } |
| 320 |
| 321 func (r *SymReader) Addr(off int) Addr { |
| 322 if r.p.ptrsize == 4 { |
| 323 return Addr(r.Uint32(off)) |
| 324 } |
| 325 return Addr(r.Uint64(off)) |
| 326 } |
| 327 |
| 328 func (r *SymReader) String(off int) string { |
| 329 end := off |
| 330 for r.data[end] != '\x00' { |
| 331 end++ |
| 332 } |
| 333 return string(r.data[off:end]) |
| 334 } |
LEFT | RIGHT |