1
2
3
4
5
8
9 package gosym
10
11 import (
12 "bytes"
13 "encoding/binary"
14 "sort"
15 "sync"
16 )
17
18
19 type version int
20
21 const (
22 verUnknown version = iota
23 ver11
24 ver12
25 ver116
26 ver118
27 )
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 type LineTable struct {
43 Data []byte
44 PC uint64
45 Line int
46
47
48 mu sync.Mutex
49
50
51 version version
52
53
54 binary binary.ByteOrder
55 quantum uint32
56 ptrsize uint32
57 textStart uint64
58 funcnametab []byte
59 cutab []byte
60 funcdata []byte
61 functab []byte
62 nfunctab uint32
63 filetab []byte
64 pctab []byte
65 nfiletab uint32
66 funcNames map[uint32]string
67 strings map[uint32]string
68
69
70
71 fileMap map[string]uint32
72 }
73
74
75
76
77
78 const oldQuantum = 1
79
80 func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) {
81
82
83
84
85
86
87
88
89 b, pc, line = t.Data, t.PC, t.Line
90 for pc <= targetPC && line != targetLine && len(b) > 0 {
91 code := b[0]
92 b = b[1:]
93 switch {
94 case code == 0:
95 if len(b) < 4 {
96 b = b[0:0]
97 break
98 }
99 val := binary.BigEndian.Uint32(b)
100 b = b[4:]
101 line += int(val)
102 case code <= 64:
103 line += int(code)
104 case code <= 128:
105 line -= int(code - 64)
106 default:
107 pc += oldQuantum * uint64(code-128)
108 continue
109 }
110 pc += oldQuantum
111 }
112 return b, pc, line
113 }
114
115 func (t *LineTable) slice(pc uint64) *LineTable {
116 data, pc, line := t.parse(pc, -1)
117 return &LineTable{Data: data, PC: pc, Line: line}
118 }
119
120
121
122
123 func (t *LineTable) PCToLine(pc uint64) int {
124 if t.isGo12() {
125 return t.go12PCToLine(pc)
126 }
127 _, _, line := t.parse(pc, -1)
128 return line
129 }
130
131
132
133
134
135 func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
136 if t.isGo12() {
137 return 0
138 }
139 _, pc, line1 := t.parse(maxpc, line)
140 if line1 != line {
141 return 0
142 }
143
144 return pc - oldQuantum
145 }
146
147
148
149
150
151 func NewLineTable(data []byte, text uint64) *LineTable {
152 return &LineTable{Data: data, PC: text, Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)}
153 }
154
155
156
157
158
159
160
161
162
163
164
165
166
167 func (t *LineTable) isGo12() bool {
168 t.parsePclnTab()
169 return t.version >= ver12
170 }
171
172 const (
173 go12magic = 0xfffffffb
174 go116magic = 0xfffffffa
175 go118magic = 0xfffffff0
176 )
177
178
179
180 func (t *LineTable) uintptr(b []byte) uint64 {
181 if t.ptrsize == 4 {
182 return uint64(t.binary.Uint32(b))
183 }
184 return t.binary.Uint64(b)
185 }
186
187
188 func (t *LineTable) parsePclnTab() {
189 t.mu.Lock()
190 defer t.mu.Unlock()
191 if t.version != verUnknown {
192 return
193 }
194
195
196
197
198
199
200 t.version = ver11
201
202 if !disableRecover {
203 defer func() {
204
205 recover()
206 }()
207 }
208
209
210 if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 ||
211 (t.Data[6] != 1 && t.Data[6] != 2 && t.Data[6] != 4) ||
212 (t.Data[7] != 4 && t.Data[7] != 8) {
213 return
214 }
215
216 var possibleVersion version
217 leMagic := binary.LittleEndian.Uint32(t.Data)
218 beMagic := binary.BigEndian.Uint32(t.Data)
219 switch {
220 case leMagic == go12magic:
221 t.binary, possibleVersion = binary.LittleEndian, ver12
222 case beMagic == go12magic:
223 t.binary, possibleVersion = binary.BigEndian, ver12
224 case leMagic == go116magic:
225 t.binary, possibleVersion = binary.LittleEndian, ver116
226 case beMagic == go116magic:
227 t.binary, possibleVersion = binary.BigEndian, ver116
228 case leMagic == go118magic:
229 t.binary, possibleVersion = binary.LittleEndian, ver118
230 case beMagic == go118magic:
231 t.binary, possibleVersion = binary.BigEndian, ver118
232 default:
233 return
234 }
235 t.version = possibleVersion
236
237
238 t.quantum = uint32(t.Data[6])
239 t.ptrsize = uint32(t.Data[7])
240
241 offset := func(word uint32) uint64 {
242 return t.uintptr(t.Data[8+word*t.ptrsize:])
243 }
244 data := func(word uint32) []byte {
245 return t.Data[offset(word):]
246 }
247
248 switch possibleVersion {
249 case ver118:
250 t.nfunctab = uint32(offset(0))
251 t.nfiletab = uint32(offset(1))
252 t.textStart = t.PC
253 t.funcnametab = data(3)
254 t.cutab = data(4)
255 t.filetab = data(5)
256 t.pctab = data(6)
257 t.funcdata = data(7)
258 t.functab = data(7)
259 functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
260 t.functab = t.functab[:functabsize]
261 case ver116:
262 t.nfunctab = uint32(offset(0))
263 t.nfiletab = uint32(offset(1))
264 t.funcnametab = data(2)
265 t.cutab = data(3)
266 t.filetab = data(4)
267 t.pctab = data(5)
268 t.funcdata = data(6)
269 t.functab = data(6)
270 functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
271 t.functab = t.functab[:functabsize]
272 case ver12:
273 t.nfunctab = uint32(t.uintptr(t.Data[8:]))
274 t.funcdata = t.Data
275 t.funcnametab = t.Data
276 t.functab = t.Data[8+t.ptrsize:]
277 t.pctab = t.Data
278 functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
279 fileoff := t.binary.Uint32(t.functab[functabsize:])
280 t.functab = t.functab[:functabsize]
281 t.filetab = t.Data[fileoff:]
282 t.nfiletab = t.binary.Uint32(t.filetab)
283 t.filetab = t.filetab[:t.nfiletab*4]
284 default:
285 panic("unreachable")
286 }
287 }
288
289
290 func (t *LineTable) go12Funcs() []Func {
291
292 if !disableRecover {
293 defer func() {
294 recover()
295 }()
296 }
297
298 ft := t.funcTab()
299 funcs := make([]Func, ft.Count())
300 syms := make([]Sym, len(funcs))
301 for i := range funcs {
302 f := &funcs[i]
303 f.Entry = ft.pc(i)
304 f.End = ft.pc(i + 1)
305 info := t.funcData(uint32(i))
306 f.LineTable = t
307 f.FrameSize = int(info.deferreturn())
308 syms[i] = Sym{
309 Value: f.Entry,
310 Type: 'T',
311 Name: t.funcName(info.nameoff()),
312 GoType: 0,
313 Func: f,
314 }
315 f.Sym = &syms[i]
316 }
317 return funcs
318 }
319
320
321 func (t *LineTable) findFunc(pc uint64) funcData {
322 ft := t.funcTab()
323 if pc < ft.pc(0) || pc >= ft.pc(ft.Count()) {
324 return funcData{}
325 }
326 idx := sort.Search(int(t.nfunctab), func(i int) bool {
327 return ft.pc(i) > pc
328 })
329 idx--
330 return t.funcData(uint32(idx))
331 }
332
333
334 func (t *LineTable) readvarint(pp *[]byte) uint32 {
335 var v, shift uint32
336 p := *pp
337 for shift = 0; ; shift += 7 {
338 b := p[0]
339 p = p[1:]
340 v |= (uint32(b) & 0x7F) << shift
341 if b&0x80 == 0 {
342 break
343 }
344 }
345 *pp = p
346 return v
347 }
348
349
350 func (t *LineTable) funcName(off uint32) string {
351 if s, ok := t.funcNames[off]; ok {
352 return s
353 }
354 i := bytes.IndexByte(t.funcnametab[off:], 0)
355 s := string(t.funcnametab[off : off+uint32(i)])
356 t.funcNames[off] = s
357 return s
358 }
359
360
361 func (t *LineTable) stringFrom(arr []byte, off uint32) string {
362 if s, ok := t.strings[off]; ok {
363 return s
364 }
365 i := bytes.IndexByte(arr[off:], 0)
366 s := string(arr[off : off+uint32(i)])
367 t.strings[off] = s
368 return s
369 }
370
371
372 func (t *LineTable) string(off uint32) string {
373 return t.stringFrom(t.funcdata, off)
374 }
375
376
377 func (t *LineTable) functabFieldSize() int {
378 if t.version >= ver118 {
379 return 4
380 }
381 return int(t.ptrsize)
382 }
383
384
385 func (t *LineTable) funcTab() funcTab {
386 return funcTab{LineTable: t, sz: t.functabFieldSize()}
387 }
388
389
390
391 type funcTab struct {
392 *LineTable
393 sz int
394 }
395
396
397 func (f funcTab) Count() int {
398 return int(f.nfunctab)
399 }
400
401
402 func (f funcTab) pc(i int) uint64 {
403 u := f.uint(f.functab[2*i*f.sz:])
404 if f.version >= ver118 {
405 u += f.textStart
406 }
407 return u
408 }
409
410
411 func (f funcTab) funcOff(i int) uint64 {
412 return f.uint(f.functab[(2*i+1)*f.sz:])
413 }
414
415
416 func (f funcTab) uint(b []byte) uint64 {
417 if f.sz == 4 {
418 return uint64(f.binary.Uint32(b))
419 }
420 return f.binary.Uint64(b)
421 }
422
423
424 type funcData struct {
425 t *LineTable
426 data []byte
427 }
428
429
430 func (t *LineTable) funcData(i uint32) funcData {
431 data := t.funcdata[t.funcTab().funcOff(int(i)):]
432 return funcData{t: t, data: data}
433 }
434
435
436 func (f funcData) IsZero() bool {
437 return f.t == nil && f.data == nil
438 }
439
440
441 func (f *funcData) entryPC() uint64 {
442
443
444 if f.t.version >= ver118 {
445
446
447 return uint64(f.t.binary.Uint32(f.data)) + f.t.textStart
448 }
449 return f.t.uintptr(f.data)
450 }
451
452 func (f funcData) nameoff() uint32 { return f.field(1) }
453 func (f funcData) deferreturn() uint32 { return f.field(3) }
454 func (f funcData) pcfile() uint32 { return f.field(5) }
455 func (f funcData) pcln() uint32 { return f.field(6) }
456 func (f funcData) cuOffset() uint32 { return f.field(8) }
457
458
459
460
461 func (f funcData) field(n uint32) uint32 {
462 if n == 0 || n > 9 {
463 panic("bad funcdata field")
464 }
465
466
467 sz0 := f.t.ptrsize
468 if f.t.version >= ver118 {
469 sz0 = 4
470 }
471 off := sz0 + (n-1)*4
472 data := f.data[off:]
473 return f.t.binary.Uint32(data)
474 }
475
476
477 func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
478 uvdelta := t.readvarint(p)
479 if uvdelta == 0 && !first {
480 return false
481 }
482 if uvdelta&1 != 0 {
483 uvdelta = ^(uvdelta >> 1)
484 } else {
485 uvdelta >>= 1
486 }
487 vdelta := int32(uvdelta)
488 pcdelta := t.readvarint(p) * t.quantum
489 *pc += uint64(pcdelta)
490 *val += vdelta
491 return true
492 }
493
494
495
496
497 func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 {
498 p := t.pctab[off:]
499
500 val := int32(-1)
501 pc := entry
502 for t.step(&p, &pc, &val, pc == entry) {
503 if targetpc < pc {
504 return val
505 }
506 }
507 return -1
508 }
509
510
511
512
513
514
515
516 func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 {
517 if filetab == 0 || linetab == 0 {
518 return 0
519 }
520
521 fp := t.pctab[filetab:]
522 fl := t.pctab[linetab:]
523 fileVal := int32(-1)
524 filePC := entry
525 lineVal := int32(-1)
526 linePC := entry
527 fileStartPC := filePC
528 for t.step(&fp, &filePC, &fileVal, filePC == entry) {
529 fileIndex := fileVal
530 if t.version == ver116 || t.version == ver118 {
531 fileIndex = int32(t.binary.Uint32(cutab[fileVal*4:]))
532 }
533 if fileIndex == filenum && fileStartPC < filePC {
534
535
536
537
538 lineStartPC := linePC
539 for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) {
540
541 if lineVal == line {
542 if fileStartPC <= lineStartPC {
543 return lineStartPC
544 }
545 if fileStartPC < linePC {
546 return fileStartPC
547 }
548 }
549 lineStartPC = linePC
550 }
551 }
552 fileStartPC = filePC
553 }
554 return 0
555 }
556
557
558 func (t *LineTable) go12PCToLine(pc uint64) (line int) {
559 defer func() {
560 if !disableRecover && recover() != nil {
561 line = -1
562 }
563 }()
564
565 f := t.findFunc(pc)
566 if f.IsZero() {
567 return -1
568 }
569 entry := f.entryPC()
570 linetab := f.pcln()
571 return int(t.pcvalue(linetab, entry, pc))
572 }
573
574
575 func (t *LineTable) go12PCToFile(pc uint64) (file string) {
576 defer func() {
577 if !disableRecover && recover() != nil {
578 file = ""
579 }
580 }()
581
582 f := t.findFunc(pc)
583 if f.IsZero() {
584 return ""
585 }
586 entry := f.entryPC()
587 filetab := f.pcfile()
588 fno := t.pcvalue(filetab, entry, pc)
589 if t.version == ver12 {
590 if fno <= 0 {
591 return ""
592 }
593 return t.string(t.binary.Uint32(t.filetab[4*fno:]))
594 }
595
596 if fno < 0 {
597 return ""
598 }
599 cuoff := f.cuOffset()
600 if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) {
601 return t.stringFrom(t.filetab, fnoff)
602 }
603 return ""
604 }
605
606
607 func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
608 defer func() {
609 if !disableRecover && recover() != nil {
610 pc = 0
611 }
612 }()
613
614 t.initFileMap()
615 filenum, ok := t.fileMap[file]
616 if !ok {
617 return 0
618 }
619
620
621
622
623 var cutab []byte
624 for i := uint32(0); i < t.nfunctab; i++ {
625 f := t.funcData(i)
626 entry := f.entryPC()
627 filetab := f.pcfile()
628 linetab := f.pcln()
629 if t.version == ver116 || t.version == ver118 {
630 cutab = t.cutab[f.cuOffset()*4:]
631 }
632 pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab)
633 if pc != 0 {
634 return pc
635 }
636 }
637 return 0
638 }
639
640
641 func (t *LineTable) initFileMap() {
642 t.mu.Lock()
643 defer t.mu.Unlock()
644
645 if t.fileMap != nil {
646 return
647 }
648 m := make(map[string]uint32)
649
650 if t.version == ver12 {
651 for i := uint32(1); i < t.nfiletab; i++ {
652 s := t.string(t.binary.Uint32(t.filetab[4*i:]))
653 m[s] = i
654 }
655 } else {
656 var pos uint32
657 for i := uint32(0); i < t.nfiletab; i++ {
658 s := t.stringFrom(t.filetab, pos)
659 m[s] = pos
660 pos += uint32(len(s) + 1)
661 }
662 }
663 t.fileMap = m
664 }
665
666
667
668
669 func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) {
670 if !disableRecover {
671 defer func() {
672 recover()
673 }()
674 }
675
676 t.initFileMap()
677 for file := range t.fileMap {
678 m[file] = obj
679 }
680 }
681
682
683
684 const disableRecover = false
685
View as plain text