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