gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/internal/gosym/pclntab.go (about) 1 package gosym 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "sort" 7 "sync" 8 ) 9 10 // version of the pclntab 11 type version int 12 13 const ( 14 verUnknown version = iota 15 ver11 16 ver12 17 ver116 18 ver118 19 ver120 20 ) 21 22 // A LineTable is a data structure mapping program counters to line numbers. 23 // 24 // In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable, 25 // and the line number corresponded to a numbering of all source lines in the 26 // program, across all files. That absolute line number would then have to be 27 // converted separately to a file name and line number within the file. 28 // 29 // In Go 1.2, the format of the data changed so that there is a single LineTable 30 // for the entire program, shared by all Funcs, and there are no absolute line 31 // numbers, just line numbers within specific files. 32 // 33 // For the most part, LineTable's methods should be treated as an internal 34 // detail of the package; callers should use the methods on Table instead. 35 type LineTable struct { 36 Data []byte 37 PC uint64 38 Line int 39 // This mutex is used to keep parsing of pclntab synchronous. 40 mu sync.Mutex 41 // Contains the version of the pclntab section. 42 version version 43 // Go 1.2/1.16/1.18 state 44 binary binary.ByteOrder 45 quantum uint32 46 ptrsize uint32 47 textStart uint64 // address of runtime.text symbol (1.18+) 48 funcnametab []byte 49 cutab []byte 50 funcdata []byte 51 functab []byte 52 nfunctab uint32 53 filetab []byte 54 pctab []byte // points to the pctables. 55 nfiletab uint32 56 funcNames map[uint32]string // cache the function names 57 strings map[uint32]string // interned substrings of Data, keyed by offset 58 // fileMap varies depending on the version of the object file. 59 // For ver12, it maps the name to the index in the file table. 60 // For ver116, it maps the name to the offset in filetab. 61 fileMap map[string]uint32 62 } 63 64 // NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4, 65 // but we have no idea whether we're using arm or not. This only 66 // matters in the old (pre-Go 1.2) symbol table format, so it's not worth 67 // fixing. 68 const oldQuantum = 1 69 70 func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) { 71 // The PC/line table can be thought of as a sequence of 72 // <pc update>* <line update> 73 // batches. Each update batch results in a (pc, line) pair, 74 // where line applies to every PC from pc up to but not 75 // including the pc of the next pair. 76 // 77 // Here we process each update individually, which simplifies 78 // the code, but makes the corner cases more confusing. 79 b, pc, line = t.Data, t.PC, t.Line 80 for pc <= targetPC && line != targetLine && len(b) > 0 { 81 code := b[0] 82 b = b[1:] 83 switch { 84 case code == 0: 85 if len(b) < 4 { 86 b = b[0:0] 87 break 88 } 89 val := binary.BigEndian.Uint32(b) 90 b = b[4:] 91 line += int(val) 92 case code <= 64: 93 line += int(code) 94 case code <= 128: 95 line -= int(code - 64) 96 default: 97 pc += oldQuantum * uint64(code-128) 98 continue 99 } 100 pc += oldQuantum 101 } 102 return b, pc, line 103 } 104 func (t *LineTable) slice(pc uint64) *LineTable { 105 data, pc, line := t.parse(pc, -1) 106 return &LineTable{Data: data, PC: pc, Line: line} 107 } 108 109 // PCToLine returns the line number for the given program counter. 110 // 111 // Deprecated: Use Table's PCToLine method instead. 112 func (t *LineTable) PCToLine(pc uint64) int { 113 if t.isGo12() { 114 return t.go12PCToLine(pc) 115 } 116 _, _, line := t.parse(pc, -1) 117 return line 118 } 119 120 // LineToPC returns the program counter for the given line number, 121 // considering only program counters before maxpc. 122 // 123 // Deprecated: Use Table's LineToPC method instead. 124 func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 { 125 if t.isGo12() { 126 return 0 127 } 128 _, pc, line1 := t.parse(maxpc, line) 129 if line1 != line { 130 return 0 131 } 132 // Subtract quantum from PC to account for post-line increment 133 return pc - oldQuantum 134 } 135 136 // NewLineTable returns a new PC/line table 137 // corresponding to the encoded data. 138 // Text must be the start address of the 139 // corresponding text segment. 140 func NewLineTable(data []byte, text uint64) *LineTable { 141 return &LineTable{Data: data, PC: text, Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)} 142 } 143 144 // Go 1.2 symbol table format. 145 // See golang.org/s/go12symtab. 146 // 147 // A general note about the methods here: rather than try to avoid 148 // index out of bounds errors, we trust Go to detect them, and then 149 // we recover from the panics and treat them as indicative of a malformed 150 // or incomplete table. 151 // 152 // The methods called by symtab.go, which begin with "go12" prefixes, 153 // are expected to have that recovery logic. 154 // isGo12 reports whether this is a Go 1.2 (or later) symbol table. 155 func (t *LineTable) isGo12() bool { 156 t.parsePclnTab() 157 return t.version >= ver12 158 } 159 160 const ( 161 go12magic = 0xfffffffb 162 go116magic = 0xfffffffa 163 go118magic = 0xfffffff0 164 go120magic = 0xfffffff1 165 ) 166 167 // uintptr returns the pointer-sized value encoded at b. 168 // The pointer size is dictated by the table being read. 169 func (t *LineTable) uintptr(b []byte) uint64 { 170 if t.ptrsize == 4 { 171 return uint64(t.binary.Uint32(b)) 172 } 173 return t.binary.Uint64(b) 174 } 175 176 // parsePclnTab parses the pclntab, setting the version. 177 func (t *LineTable) parsePclnTab() { 178 t.mu.Lock() 179 defer t.mu.Unlock() 180 if t.version != verUnknown { 181 return 182 } 183 // Note that during this function, setting the version is the last thing we do. 184 // If we set the version too early, and parsing failed (likely as a panic on 185 // slice lookups), we'd have a mistaken version. 186 // 187 // Error paths through this code will default the version to 1.1. 188 t.version = ver11 189 if !disableRecover { 190 defer func() { 191 // If we panic parsing, assume it's a Go 1.1 pclntab. 192 _ = recover() 193 }() 194 } 195 // Check header: 4-byte magic, two zeros, pc quantum, pointer size. 196 if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 || 197 (t.Data[6] != 1 && t.Data[6] != 2 && t.Data[6] != 4) || // pc quantum 198 (t.Data[7] != 4 && t.Data[7] != 8) { // pointer size 199 return 200 } 201 var possibleVersion version 202 leMagic := binary.LittleEndian.Uint32(t.Data) 203 beMagic := binary.BigEndian.Uint32(t.Data) 204 switch { 205 case leMagic == go12magic: 206 t.binary, possibleVersion = binary.LittleEndian, ver12 207 case beMagic == go12magic: 208 t.binary, possibleVersion = binary.BigEndian, ver12 209 case leMagic == go116magic: 210 t.binary, possibleVersion = binary.LittleEndian, ver116 211 case beMagic == go116magic: 212 t.binary, possibleVersion = binary.BigEndian, ver116 213 case leMagic == go118magic: 214 t.binary, possibleVersion = binary.LittleEndian, ver118 215 case beMagic == go118magic: 216 t.binary, possibleVersion = binary.BigEndian, ver118 217 case leMagic == go120magic: 218 t.binary, possibleVersion = binary.LittleEndian, ver120 219 case beMagic == go120magic: 220 t.binary, possibleVersion = binary.BigEndian, ver120 221 default: 222 return 223 } 224 t.version = possibleVersion 225 // quantum and ptrSize are the same between 1.2, 1.16, and 1.18 226 t.quantum = uint32(t.Data[6]) 227 t.ptrsize = uint32(t.Data[7]) 228 offset := func(word uint32) uint64 { 229 return t.uintptr(t.Data[8+word*t.ptrsize:]) 230 } 231 data := func(word uint32) []byte { 232 return t.Data[offset(word):] 233 } 234 switch possibleVersion { 235 case ver118, ver120: 236 t.nfunctab = uint32(offset(0)) 237 t.nfiletab = uint32(offset(1)) 238 t.textStart = t.PC // use the start PC instead of reading from the table, which may be unrelocated 239 t.funcnametab = data(3) 240 t.cutab = data(4) 241 t.filetab = data(5) 242 t.pctab = data(6) 243 t.funcdata = data(7) 244 t.functab = data(7) 245 functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize() 246 t.functab = t.functab[:functabsize] 247 case ver116: 248 t.nfunctab = uint32(offset(0)) 249 t.nfiletab = uint32(offset(1)) 250 t.funcnametab = data(2) 251 t.cutab = data(3) 252 t.filetab = data(4) 253 t.pctab = data(5) 254 t.funcdata = data(6) 255 t.functab = data(6) 256 functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize() 257 t.functab = t.functab[:functabsize] 258 case ver12: 259 t.nfunctab = uint32(t.uintptr(t.Data[8:])) 260 t.funcdata = t.Data 261 t.funcnametab = t.Data 262 t.functab = t.Data[8+t.ptrsize:] 263 t.pctab = t.Data 264 functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize() 265 fileoff := t.binary.Uint32(t.functab[functabsize:]) 266 t.functab = t.functab[:functabsize] 267 t.filetab = t.Data[fileoff:] 268 t.nfiletab = t.binary.Uint32(t.filetab) 269 t.filetab = t.filetab[:t.nfiletab*4] 270 default: 271 panic("unreachable") 272 } 273 } 274 275 // go12Funcs returns a slice of Funcs derived from the Go 1.2+ pcln table. 276 func (t *LineTable) go12Funcs() []Func { 277 // Assume it is malformed and return nil on error. 278 if !disableRecover { 279 defer func() { 280 _ = recover() 281 }() 282 } 283 ft := t.funcTab() 284 funcs := make([]Func, ft.Count()) 285 syms := make([]Sym, len(funcs)) 286 for i := range funcs { 287 f := &funcs[i] 288 f.Entry = ft.pc(i) 289 f.End = ft.pc(i + 1) 290 info := t.funcData(uint32(i)) 291 f.LineTable = t 292 f.FrameSize = int(info.deferreturn()) 293 // Additions: 294 // numFuncField is the number of (32 bit) fields in _func (src/runtime/runtime2.go) 295 // Note that the last 4 fields are 32 bits combined. This number is 11 for go1.20, 296 // 10 for earlier versions down to go1.16, and 9 before that. 297 var numFuncFields uint32 = 11 298 if t.version < ver116 { 299 numFuncFields = 9 300 } else if t.version < ver120 { 301 numFuncFields = 10 302 } 303 f.inlineTreeOffset = info.funcdataOffset(funcdata_InlTree, numFuncFields) 304 f.inlineTreeCount = 1 + t.maxInlineTreeIndexValue(info, numFuncFields) 305 syms[i] = Sym{ 306 Value: f.Entry, 307 Type: 'T', 308 Name: t.funcName(info.nameOff()), 309 GoType: 0, 310 Func: f, 311 goVersion: t.version, 312 } 313 f.Sym = &syms[i] 314 } 315 return funcs 316 } 317 318 // findFunc returns the funcData corresponding to the given program counter. 319 func (t *LineTable) findFunc(pc uint64) funcData { 320 ft := t.funcTab() 321 if pc < ft.pc(0) || pc >= ft.pc(ft.Count()) { 322 return funcData{} 323 } 324 idx := sort.Search(int(t.nfunctab), func(i int) bool { 325 return ft.pc(i) > pc 326 }) 327 idx-- 328 return t.funcData(uint32(idx)) 329 } 330 331 // readvarint reads, removes, and returns a varint from *pp. 332 func (t *LineTable) readvarint(pp *[]byte) uint32 { 333 var v, shift uint32 334 p := *pp 335 for shift = 0; ; shift += 7 { 336 b := p[0] 337 p = p[1:] 338 v |= (uint32(b) & 0x7F) << shift 339 if b&0x80 == 0 { 340 break 341 } 342 } 343 *pp = p 344 return v 345 } 346 347 // funcName returns the name of the function found at off. 348 func (t *LineTable) funcName(off uint32) string { 349 if s, ok := t.funcNames[off]; ok { 350 return s 351 } 352 i := bytes.IndexByte(t.funcnametab[off:], 0) 353 s := string(t.funcnametab[off : off+uint32(i)]) 354 t.funcNames[off] = s 355 return s 356 } 357 358 // stringFrom returns a Go string found at off from a position. 359 func (t *LineTable) stringFrom(arr []byte, off uint32) string { 360 if s, ok := t.strings[off]; ok { 361 return s 362 } 363 i := bytes.IndexByte(arr[off:], 0) 364 s := string(arr[off : off+uint32(i)]) 365 t.strings[off] = s 366 return s 367 } 368 369 // string returns a Go string found at off. 370 func (t *LineTable) string(off uint32) string { 371 return t.stringFrom(t.funcdata, off) 372 } 373 374 // functabFieldSize returns the size in bytes of a single functab field. 375 func (t *LineTable) functabFieldSize() int { 376 if t.version >= ver118 { 377 return 4 378 } 379 return int(t.ptrsize) 380 } 381 382 // funcTab returns t's funcTab. 383 func (t *LineTable) funcTab() funcTab { 384 return funcTab{LineTable: t, sz: t.functabFieldSize()} 385 } 386 387 // funcTab is memory corresponding to a slice of functab structs, followed by an invalid PC. 388 // A functab struct is a PC and a func offset. 389 type funcTab struct { 390 *LineTable 391 sz int // cached result of t.functabFieldSize 392 } 393 394 // Count returns the number of func entries in f. 395 func (f funcTab) Count() int { 396 return int(f.nfunctab) 397 } 398 399 // pc returns the PC of the i'th func in f. 400 func (f funcTab) pc(i int) uint64 { 401 u := f.uint(f.functab[2*i*f.sz:]) 402 if f.version >= ver118 { 403 u += f.textStart 404 } 405 return u 406 } 407 408 // funcOff returns the funcdata offset of the i'th func in f. 409 func (f funcTab) funcOff(i int) uint64 { 410 return f.uint(f.functab[(2*i+1)*f.sz:]) 411 } 412 413 // uint returns the uint stored at b. 414 func (f funcTab) uint(b []byte) uint64 { 415 if f.sz == 4 { 416 return uint64(f.binary.Uint32(b)) 417 } 418 return f.binary.Uint64(b) 419 } 420 421 // funcData is memory corresponding to an _func struct. 422 type funcData struct { 423 t *LineTable // LineTable this data is a part of 424 data []byte // raw memory for the function 425 } 426 427 // funcData returns the ith funcData in t.functab. 428 func (t *LineTable) funcData(i uint32) funcData { 429 data := t.funcdata[t.funcTab().funcOff(int(i)):] 430 return funcData{t: t, data: data} 431 } 432 433 // IsZero reports whether f is the zero value. 434 func (f funcData) IsZero() bool { 435 return f.t == nil && f.data == nil 436 } 437 438 // entryPC returns the func's entry PC. 439 func (f *funcData) entryPC() uint64 { 440 // In Go 1.18, the first field of _func changed 441 // from a uintptr entry PC to a uint32 entry offset. 442 if f.t.version >= ver118 { 443 // TODO: support multiple text sections. 444 // See runtime/symtab.go:(*moduledata).textAddr. 445 return uint64(f.t.binary.Uint32(f.data)) + f.t.textStart 446 } 447 return f.t.uintptr(f.data) 448 } 449 func (f funcData) nameOff() uint32 { return f.field(1) } 450 func (f funcData) deferreturn() uint32 { return f.field(3) } 451 func (f funcData) pcfile() uint32 { return f.field(5) } 452 func (f funcData) pcln() uint32 { return f.field(6) } 453 func (f funcData) cuOffset() uint32 { return f.field(8) } 454 455 // field returns the nth field of the _func struct. 456 // It panics if n == 0 or n > 9; for n == 0, call f.entryPC. 457 // Most callers should use a named field accessor (just above). 458 func (f funcData) field(n uint32) uint32 { 459 if n == 0 || n > 9 { 460 panic("bad funcdata field") 461 } 462 // Addition: some code deleted here to support inlining. 463 off := f.fieldOffset(n) 464 data := f.data[off:] 465 return f.t.binary.Uint32(data) 466 } 467 468 // step advances to the next pc, value pair in the encoded table. 469 func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { 470 uvdelta := t.readvarint(p) 471 if uvdelta == 0 && !first { 472 return false 473 } 474 if uvdelta&1 != 0 { 475 uvdelta = ^(uvdelta >> 1) 476 } else { 477 uvdelta >>= 1 478 } 479 vdelta := int32(uvdelta) 480 pcdelta := t.readvarint(p) * t.quantum 481 *pc += uint64(pcdelta) 482 *val += vdelta 483 return true 484 } 485 486 // pcvalue reports the value associated with the target pc. 487 // off is the offset to the beginning of the pc-value table, 488 // and entry is the start PC for the corresponding function. 489 func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { 490 p := t.pctab[off:] 491 val := int32(-1) 492 pc := entry 493 for t.step(&p, &pc, &val, pc == entry) { 494 if targetpc < pc { 495 return val 496 } 497 } 498 return -1 499 } 500 501 // findFileLine scans one function in the binary looking for a 502 // program counter in the given file on the given line. 503 // It does so by running the pc-value tables mapping program counter 504 // to file number. Since most functions come from a single file, these 505 // are usually short and quick to scan. If a file match is found, then the 506 // code goes to the expense of looking for a simultaneous line number match. 507 func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 { 508 if filetab == 0 || linetab == 0 { 509 return 0 510 } 511 fp := t.pctab[filetab:] 512 fl := t.pctab[linetab:] 513 fileVal := int32(-1) 514 filePC := entry 515 lineVal := int32(-1) 516 linePC := entry 517 fileStartPC := filePC 518 for t.step(&fp, &filePC, &fileVal, filePC == entry) { 519 fileIndex := fileVal 520 if t.version == ver116 || t.version == ver118 || t.version == ver120 { 521 fileIndex = int32(t.binary.Uint32(cutab[fileVal*4:])) 522 } 523 if fileIndex == filenum && fileStartPC < filePC { 524 // fileIndex is in effect starting at fileStartPC up to 525 // but not including filePC, and it's the file we want. 526 // Run the PC table looking for a matching line number 527 // or until we reach filePC. 528 lineStartPC := linePC 529 for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) { 530 // lineVal is in effect until linePC, and lineStartPC < filePC. 531 if lineVal == line { 532 if fileStartPC <= lineStartPC { 533 return lineStartPC 534 } 535 if fileStartPC < linePC { 536 return fileStartPC 537 } 538 } 539 lineStartPC = linePC 540 } 541 } 542 fileStartPC = filePC 543 } 544 return 0 545 } 546 547 // go12PCToLine maps program counter to line number for the Go 1.2+ pcln table. 548 func (t *LineTable) go12PCToLine(pc uint64) (line int) { 549 defer func() { 550 if !disableRecover && recover() != nil { 551 line = -1 552 } 553 }() 554 f := t.findFunc(pc) 555 if f.IsZero() { 556 return -1 557 } 558 entry := f.entryPC() 559 linetab := f.pcln() 560 return int(t.pcvalue(linetab, entry, pc)) 561 } 562 563 // go12PCToFile maps program counter to file name for the Go 1.2+ pcln table. 564 func (t *LineTable) go12PCToFile(pc uint64) (file string) { 565 defer func() { 566 if !disableRecover && recover() != nil { 567 file = "" 568 } 569 }() 570 f := t.findFunc(pc) 571 if f.IsZero() { 572 return "" 573 } 574 entry := f.entryPC() 575 filetab := f.pcfile() 576 fno := t.pcvalue(filetab, entry, pc) 577 if t.version == ver12 { 578 if fno <= 0 { 579 return "" 580 } 581 return t.string(t.binary.Uint32(t.filetab[4*fno:])) 582 } 583 // Go ≥ 1.16 584 if fno < 0 { // 0 is valid for ≥ 1.16 585 return "" 586 } 587 cuoff := f.cuOffset() 588 if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) { 589 return t.stringFrom(t.filetab, fnoff) 590 } 591 return "" 592 } 593 594 // go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2+ pcln table. 595 func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) { 596 defer func() { 597 if !disableRecover && recover() != nil { 598 pc = 0 599 } 600 }() 601 t.initFileMap() 602 filenum, ok := t.fileMap[file] 603 if !ok { 604 return 0 605 } 606 // Scan all functions. 607 // If this turns out to be a bottleneck, we could build a map[int32][]int32 608 // mapping file number to a list of functions with code from that file. 609 var cutab []byte 610 for i := uint32(0); i < t.nfunctab; i++ { 611 f := t.funcData(i) 612 entry := f.entryPC() 613 filetab := f.pcfile() 614 linetab := f.pcln() 615 if t.version == ver116 || t.version == ver118 || t.version == ver120 { 616 if f.cuOffset() == ^uint32(0) { 617 // skip functions without compilation unit (not real function, or linker generated) 618 continue 619 } 620 cutab = t.cutab[f.cuOffset()*4:] 621 } 622 pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab) 623 if pc != 0 { 624 return pc 625 } 626 } 627 return 0 628 } 629 630 // initFileMap initializes the map from file name to file number. 631 func (t *LineTable) initFileMap() { 632 t.mu.Lock() 633 defer t.mu.Unlock() 634 if t.fileMap != nil { 635 return 636 } 637 m := make(map[string]uint32) 638 if t.version == ver12 { 639 for i := uint32(1); i < t.nfiletab; i++ { 640 s := t.string(t.binary.Uint32(t.filetab[4*i:])) 641 m[s] = i 642 } 643 } else { 644 var pos uint32 645 for i := uint32(0); i < t.nfiletab; i++ { 646 s := t.stringFrom(t.filetab, pos) 647 m[s] = pos 648 pos += uint32(len(s) + 1) 649 } 650 } 651 t.fileMap = m 652 } 653 654 // go12MapFiles adds to m a key for every file in the Go 1.2 LineTable. 655 // Every key maps to obj. That's not a very interesting map, but it provides 656 // a way for callers to obtain the list of files in the program. 657 func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) { 658 if !disableRecover { 659 defer func() { 660 _ = recover() 661 }() 662 } 663 t.initFileMap() 664 for file := range t.fileMap { 665 m[file] = obj 666 } 667 } 668 669 // disableRecover causes this package not to swallow panics. 670 // This is useful when making changes. 671 const disableRecover = true