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