// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file./* * Line tables */package gosymimport ()// version of the pclntabtype version intconst ( verUnknown version = iota ver11 ver12 ver116 ver118 ver120)// A LineTable is a data structure mapping program counters to line numbers.//// In Go 1.1 and earlier, each function (represented by a [Func]) had its own LineTable,// and the line number corresponded to a numbering of all source lines in the// program, across all files. That absolute line number would then have to be// converted separately to a file name and line number within the file.//// In Go 1.2, the format of the data changed so that there is a single LineTable// for the entire program, shared by all Funcs, and there are no absolute line// numbers, just line numbers within specific files.//// For the most part, LineTable's methods should be treated as an internal// detail of the package; callers should use the methods on [Table] instead.typeLineTablestruct { Data []byte PC uint64 Line int// This mutex is used to keep parsing of pclntab synchronous. mu sync.Mutex// Contains the version of the pclntab section. version version// Go 1.2/1.16/1.18 state binary binary.ByteOrder quantum uint32 ptrsize uint32 textStart uint64// address of runtime.text symbol (1.18+) funcnametab []byte cutab []byte funcdata []byte functab []byte nfunctab uint32 filetab []byte pctab []byte// points to the pctables. nfiletab uint32 funcNames map[uint32]string// cache the function names strings map[uint32]string// interned substrings of Data, keyed by offset// fileMap varies depending on the version of the object file. // For ver12, it maps the name to the index in the file table. // For ver116, it maps the name to the offset in filetab. fileMap map[string]uint32}// NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4,// but we have no idea whether we're using arm or not. This only// matters in the old (pre-Go 1.2) symbol table format, so it's not worth// fixing.const oldQuantum = 1func ( *LineTable) ( uint64, int) ( []byte, uint64, int) {// The PC/line table can be thought of as a sequence of // <pc update>* <line update> // batches. Each update batch results in a (pc, line) pair, // where line applies to every PC from pc up to but not // including the pc of the next pair. // // Here we process each update individually, which simplifies // the code, but makes the corner cases more confusing. , , = .Data, .PC, .Linefor <= && != && len() > 0 { := [0] = [1:]switch {case == 0:iflen() < 4 { = [0:0]break } := binary.BigEndian.Uint32() = [4:] += int()case <= 64: += int()case <= 128: -= int( - 64)default: += oldQuantum * uint64(-128)continue } += oldQuantum }return , , }func ( *LineTable) ( uint64) *LineTable { , , := .parse(, -1)return &LineTable{Data: , PC: , Line: }}// PCToLine returns the line number for the given program counter.//// Deprecated: Use Table's PCToLine method instead.func ( *LineTable) ( uint64) int {if .isGo12() {return .go12PCToLine() } , , := .parse(, -1)return}// LineToPC returns the program counter for the given line number,// considering only program counters before maxpc.//// Deprecated: Use Table's LineToPC method instead.func ( *LineTable) ( int, uint64) uint64 {if .isGo12() {return0 } , , := .parse(, )if != {return0 }// Subtract quantum from PC to account for post-line incrementreturn - oldQuantum}// NewLineTable returns a new PC/line table// corresponding to the encoded data.// Text must be the start address of the// corresponding text segment, with the exact// value stored in the 'runtime.text' symbol.// This value may differ from the start// address of the text segment if// binary was built with cgo enabled.func ( []byte, uint64) *LineTable {return &LineTable{Data: , PC: , Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)}}// Go 1.2 symbol table format.// See golang.org/s/go12symtab.//// A general note about the methods here: rather than try to avoid// index out of bounds errors, we trust Go to detect them, and then// we recover from the panics and treat them as indicative of a malformed// or incomplete table.//// The methods called by symtab.go, which begin with "go12" prefixes,// are expected to have that recovery logic.// isGo12 reports whether this is a Go 1.2 (or later) symbol table.func ( *LineTable) () bool { .parsePclnTab()return .version >= ver12}const ( go12magic = 0xfffffffb go116magic = 0xfffffffa go118magic = 0xfffffff0 go120magic = 0xfffffff1)// uintptr returns the pointer-sized value encoded at b.// The pointer size is dictated by the table being read.func ( *LineTable) ( []byte) uint64 {if .ptrsize == 4 {returnuint64(.binary.Uint32()) }return .binary.Uint64()}// parsePclnTab parses the pclntab, setting the version.func ( *LineTable) () { .mu.Lock()defer .mu.Unlock()if .version != verUnknown {return }// Note that during this function, setting the version is the last thing we do. // If we set the version too early, and parsing failed (likely as a panic on // slice lookups), we'd have a mistaken version. // // Error paths through this code will default the version to 1.1. .version = ver11if !disableRecover {deferfunc() {// If we panic parsing, assume it's a Go 1.1 pclntab.recover() }() }// Check header: 4-byte magic, two zeros, pc quantum, pointer size.iflen(.Data) < 16 || .Data[4] != 0 || .Data[5] != 0 || (.Data[6] != 1 && .Data[6] != 2 && .Data[6] != 4) || // pc quantum (.Data[7] != 4 && .Data[7] != 8) { // pointer sizereturn }varversion := binary.LittleEndian.Uint32(.Data) := binary.BigEndian.Uint32(.Data)switch {case == go12magic: .binary, = binary.LittleEndian, ver12case == go12magic: .binary, = binary.BigEndian, ver12case == go116magic: .binary, = binary.LittleEndian, ver116case == go116magic: .binary, = binary.BigEndian, ver116case == go118magic: .binary, = binary.LittleEndian, ver118case == go118magic: .binary, = binary.BigEndian, ver118case == go120magic: .binary, = binary.LittleEndian, ver120case == go120magic: .binary, = binary.BigEndian, ver120default:return } .version = // quantum and ptrSize are the same between 1.2, 1.16, and 1.18 .quantum = uint32(.Data[6]) .ptrsize = uint32(.Data[7]) := func( uint32) uint64 {return .uintptr(.Data[8+*.ptrsize:]) } := func( uint32) []byte {return .Data[():] }switch {casever118, ver120: .nfunctab = uint32((0)) .nfiletab = uint32((1)) .textStart = .PC// use the start PC instead of reading from the table, which may be unrelocated .funcnametab = (3) .cutab = (4) .filetab = (5) .pctab = (6) .funcdata = (7) .functab = (7) := (int(.nfunctab)*2 + 1) * .functabFieldSize() .functab = .functab[:]casever116: .nfunctab = uint32((0)) .nfiletab = uint32((1)) .funcnametab = (2) .cutab = (3) .filetab = (4) .pctab = (5) .funcdata = (6) .functab = (6) := (int(.nfunctab)*2 + 1) * .functabFieldSize() .functab = .functab[:]casever12: .nfunctab = uint32(.uintptr(.Data[8:])) .funcdata = .Data .funcnametab = .Data .functab = .Data[8+.ptrsize:] .pctab = .Data := (int(.nfunctab)*2 + 1) * .functabFieldSize() := .binary.Uint32(.functab[:]) .functab = .functab[:] .filetab = .Data[:] .nfiletab = .binary.Uint32(.filetab) .filetab = .filetab[:.nfiletab*4]default:panic("unreachable") }}// go12Funcs returns a slice of Funcs derived from the Go 1.2+ pcln table.func ( *LineTable) () []Func {// Assume it is malformed and return nil on error.if !disableRecover {deferfunc() {recover() }() } := .funcTab() := make([]Func, .Count()) := make([]Sym, len())for := range { := &[] .Entry = .pc() .End = .pc( + 1) := .funcData(uint32()) .LineTable = .FrameSize = int(.deferreturn()) [] = Sym{Value: .Entry,Type: 'T',Name: .funcName(.nameOff()),GoType: 0,Func: ,goVersion: .version, } .Sym = &[] }return}// findFunc returns the funcData corresponding to the given program counter.func ( *LineTable) ( uint64) funcData { := .funcTab()if < .pc(0) || >= .pc(.Count()) {returnfuncData{} } := sort.Search(int(.nfunctab), func( int) bool {return .pc() > }) --return .funcData(uint32())}// readvarint reads, removes, and returns a varint from *pp.func ( *LineTable) ( *[]byte) uint32 {var , uint32 := *for = 0; ; += 7 { := [0] = [1:] |= (uint32() & 0x7F) << if &0x80 == 0 {break } } * = return}// funcName returns the name of the function found at off.func ( *LineTable) ( uint32) string {if , := .funcNames[]; {return } := bytes.IndexByte(.funcnametab[:], 0) := string(.funcnametab[ : +uint32()]) .funcNames[] = return}// stringFrom returns a Go string found at off from a position.func ( *LineTable) ( []byte, uint32) string {if , := .strings[]; {return } := bytes.IndexByte([:], 0) := string([ : +uint32()]) .strings[] = return}// string returns a Go string found at off.func ( *LineTable) ( uint32) string {return .stringFrom(.funcdata, )}// functabFieldSize returns the size in bytes of a single functab field.func ( *LineTable) () int {if .version >= ver118 {return4 }returnint(.ptrsize)}// funcTab returns t's funcTab.func ( *LineTable) () funcTab {returnfuncTab{LineTable: , sz: .functabFieldSize()}}// funcTab is memory corresponding to a slice of functab structs, followed by an invalid PC.// A functab struct is a PC and a func offset.type funcTab struct { *LineTable sz int// cached result of t.functabFieldSize}// Count returns the number of func entries in f.func ( funcTab) () int {returnint(.nfunctab)}// pc returns the PC of the i'th func in f.func ( funcTab) ( int) uint64 { := .uint(.functab[2**.sz:])if .version >= ver118 { += .textStart }return}// funcOff returns the funcdata offset of the i'th func in f.func ( funcTab) ( int) uint64 {return .uint(.functab[(2*+1)*.sz:])}// uint returns the uint stored at b.func ( funcTab) ( []byte) uint64 {if .sz == 4 {returnuint64(.binary.Uint32()) }return .binary.Uint64()}// funcData is memory corresponding to an _func struct.type funcData struct { t *LineTable// LineTable this data is a part of data []byte// raw memory for the function}// funcData returns the ith funcData in t.functab.func ( *LineTable) ( uint32) funcData { := .funcdata[.funcTab().funcOff(int()):]returnfuncData{t: , data: }}// IsZero reports whether f is the zero value.func ( funcData) () bool {return .t == nil && .data == nil}// entryPC returns the func's entry PC.func ( *funcData) () uint64 {// In Go 1.18, the first field of _func changed // from a uintptr entry PC to a uint32 entry offset.if .t.version >= ver118 {// TODO: support multiple text sections. // See runtime/symtab.go:(*moduledata).textAddr.returnuint64(.t.binary.Uint32(.data)) + .t.textStart }return .t.uintptr(.data)}func ( funcData) () uint32 { return .field(1) }func ( funcData) () uint32 { return .field(3) }func ( funcData) () uint32 { return .field(5) }func ( funcData) () uint32 { return .field(6) }func ( funcData) () uint32 { return .field(8) }// field returns the nth field of the _func struct.// It panics if n == 0 or n > 9; for n == 0, call f.entryPC.// Most callers should use a named field accessor (just above).func ( funcData) ( uint32) uint32 {if == 0 || > 9 {panic("bad funcdata field") }// In Go 1.18, the first field of _func changed // from a uintptr entry PC to a uint32 entry offset. := .t.ptrsizeif .t.version >= ver118 { = 4 } := + (-1)*4// subsequent fields are 4 bytes each := .data[:]return .t.binary.Uint32()}// step advances to the next pc, value pair in the encoded table.func ( *LineTable) ( *[]byte, *uint64, *int32, bool) bool { := .readvarint()if == 0 && ! {returnfalse }if &1 != 0 { = ^( >> 1) } else { >>= 1 } := int32() := .readvarint() * .quantum * += uint64() * += returntrue}// pcvalue reports the value associated with the target pc.// off is the offset to the beginning of the pc-value table,// and entry is the start PC for the corresponding function.func ( *LineTable) ( uint32, , uint64) int32 { := .pctab[:] := int32(-1) := for .step(&, &, &, == ) {if < {return } }return -1}// findFileLine scans one function in the binary looking for a// program counter in the given file on the given line.// It does so by running the pc-value tables mapping program counter// to file number. Since most functions come from a single file, these// are usually short and quick to scan. If a file match is found, then the// code goes to the expense of looking for a simultaneous line number match.func ( *LineTable) ( uint64, , uint32, , int32, []byte) uint64 {if == 0 || == 0 {return0 } := .pctab[:] := .pctab[:] := int32(-1) := := int32(-1) := := for .step(&, &, &, == ) { := if .version == ver116 || .version == ver118 || .version == ver120 { = int32(.binary.Uint32([*4:])) }if == && < {// fileIndex is in effect starting at fileStartPC up to // but not including filePC, and it's the file we want. // Run the PC table looking for a matching line number // or until we reach filePC. := for < && .step(&, &, &, == ) {// lineVal is in effect until linePC, and lineStartPC < filePC.if == {if <= {return }if < {return } } = } } = }return0}// go12PCToLine maps program counter to line number for the Go 1.2+ pcln table.func ( *LineTable) ( uint64) ( int) {deferfunc() {if !disableRecover && recover() != nil { = -1 } }() := .findFunc()if .IsZero() {return -1 } := .entryPC() := .pcln()returnint(.pcvalue(, , ))}// go12PCToFile maps program counter to file name for the Go 1.2+ pcln table.func ( *LineTable) ( uint64) ( string) {deferfunc() {if !disableRecover && recover() != nil { = "" } }() := .findFunc()if .IsZero() {return"" } := .entryPC() := .pcfile() := .pcvalue(, , )if .version == ver12 {if <= 0 {return"" }return .string(.binary.Uint32(.filetab[4*:])) }// Go ≥ 1.16if < 0 { // 0 is valid for ≥ 1.16return"" } := .cuOffset()if := .binary.Uint32(.cutab[(+uint32())*4:]); != ^uint32(0) {return .stringFrom(.filetab, ) }return""}// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2+ pcln table.func ( *LineTable) ( string, int) ( uint64) {deferfunc() {if !disableRecover && recover() != nil { = 0 } }() .initFileMap() , := .fileMap[]if ! {return0 }// Scan all functions. // If this turns out to be a bottleneck, we could build a map[int32][]int32 // mapping file number to a list of functions with code from that file.var []bytefor := uint32(0); < .nfunctab; ++ { := .funcData() := .entryPC() := .pcfile() := .pcln()if .version == ver116 || .version == ver118 || .version == ver120 {if .cuOffset() == ^uint32(0) {// skip functions without compilation unit (not real function, or linker generated)continue } = .cutab[.cuOffset()*4:] } := .findFileLine(, , , int32(), int32(), )if != 0 {return } }return0}// initFileMap initializes the map from file name to file number.func ( *LineTable) () { .mu.Lock()defer .mu.Unlock()if .fileMap != nil {return } := make(map[string]uint32)if .version == ver12 {for := uint32(1); < .nfiletab; ++ { := .string(.binary.Uint32(.filetab[4*:])) [] = } } else {varuint32for := uint32(0); < .nfiletab; ++ { := .stringFrom(.filetab, ) [] = += uint32(len() + 1) } } .fileMap = }// go12MapFiles adds to m a key for every file in the Go 1.2 LineTable.// Every key maps to obj. That's not a very interesting map, but it provides// a way for callers to obtain the list of files in the program.func ( *LineTable) ( map[string]*Obj, *Obj) {if !disableRecover {deferfunc() {recover() }() } .initFileMap()for := range .fileMap { [] = }}// disableRecover causes this package not to swallow panics.// This is useful when making changes.const disableRecover = false
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.