// 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.
// Package gosym implements access to the Go symbol// and line number tables embedded in Go binaries generated// by the gc compilers.
package gosymimport ()/* * Symbols */// A Sym represents a single symbol table entry.typeSymstruct { Value uint64 Type byte Name string GoType uint64// If this symbol is a function symbol, the corresponding Func Func *Func goVersion version}// Static reports whether this symbol is static (not visible outside its file).func ( *Sym) () bool { return .Type >= 'a' }// nameWithoutInst returns s.Name if s.Name has no brackets (does not reference an// instantiated type, function, or method). If s.Name contains brackets, then it// returns s.Name with all the contents between (and including) the outermost left// and right bracket removed. This is useful to ignore any extra slashes or dots// inside the brackets from the string searches below, where needed.func ( *Sym) () string { := strings.Index(.Name, "[")if < 0 {return .Name } := strings.LastIndex(.Name, "]")if < 0 {// Malformed name, should contain closing bracket too.return .Name }return .Name[0:] + .Name[+1:]}// PackageName returns the package part of the symbol name,// or the empty string if there is none.func ( *Sym) () string { := .nameWithoutInst()// Since go1.20, a prefix of "type:" and "go:" is a compiler-generated symbol, // they do not belong to any package. // // See cmd/compile/internal/base/link.go:ReservedImports variable.if .goVersion >= ver120 && (strings.HasPrefix(, "go:") || strings.HasPrefix(, "type:")) {return"" }// For go1.18 and below, the prefix are "type." and "go." instead.if .goVersion <= ver118 && (strings.HasPrefix(, "go.") || strings.HasPrefix(, "type.")) {return"" } := strings.LastIndex(, "/")if < 0 { = 0 }if := strings.Index([:], "."); != -1 {return [:+] }return""}// ReceiverName returns the receiver type name of this symbol,// or the empty string if there is none. A receiver name is only detected in// the case that s.Name is fully-specified with a package name.func ( *Sym) () string { := .nameWithoutInst()// If we find a slash in name, it should precede any bracketed expression // that was removed, so pathend will apply correctly to name and s.Name. := strings.LastIndex(, "/")if < 0 { = 0 }// Find the first dot after pathend (or from the beginning, if there was // no slash in name). := strings.Index([:], ".")// Find the last dot after pathend (or the beginning). := strings.LastIndex([:], ".")if == -1 || == -1 || == {// There is no receiver if we didn't find two distinct dots after pathend.return"" }// Given there is a trailing '.' that is in name, find it now in s.Name. // pathend+l should apply to s.Name, because it should be the dot in the // package name. = strings.LastIndex(.Name[:], ".")return .Name[++1 : +]}// BaseName returns the symbol name without the package or receiver name.func ( *Sym) () string { := .nameWithoutInst()if := strings.LastIndex(, "."); != -1 {if .Name != { := strings.Index(.Name, "[")if > {// BaseName is a method name after the brackets, so // recalculate for s.Name. Otherwise, i applies // correctly to s.Name, since it is before the // brackets. = strings.LastIndex(.Name, ".") } }return .Name[+1:] }return .Name}// A Func collects information about a single function.typeFuncstruct { Entry uint64 *Sym End uint64 Params []*Sym// nil for Go 1.3 and later binaries Locals []*Sym// nil for Go 1.3 and later binaries FrameSize int LineTable *LineTable Obj *Obj}// An Obj represents a collection of functions in a symbol table.//// The exact method of division of a binary into separate Objs is an internal detail// of the symbol table format.//// In early versions of Go each source file became a different Obj.//// In Go 1 and Go 1.1, each package produced one Obj for all Go sources// and one Obj per C source file.//// In Go 1.2, there is a single Obj for the entire program.typeObjstruct {// Funcs is a list of functions in the Obj. Funcs []Func// In Go 1.1 and earlier, Paths is a list of symbols corresponding // to the source file names that produced the Obj. // In Go 1.2, Paths is nil. // Use the keys of Table.Files to obtain a list of source files. Paths []Sym// meta}/* * Symbol tables */// Table represents a Go symbol table. It stores all of the// symbols decoded from the program and provides methods to translate// between symbols, names, and addresses.typeTablestruct { Syms []Sym// nil for Go 1.3 and later binaries Funcs []Func Files map[string]*Obj// for Go 1.2 and later all files map to one Obj Objs []Obj// for Go 1.2 and later only one Obj in slice go12line *LineTable// Go 1.2 line number table}type sym struct { value uint64 gotype uint64 typ byte name []byte}var ( littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00} bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00} oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00})func walksymtab( []byte, func(sym) error) error {iflen() == 0 { // missing symtab is okayreturnnil }varbinary.ByteOrder = binary.BigEndian := falseswitch {casebytes.HasPrefix(, oldLittleEndianSymtab):// Same as Go 1.0, but little endian. // Format was used during interim development between Go 1.0 and Go 1.1. // Should not be widespread, but easy to support. = [6:] = binary.LittleEndiancasebytes.HasPrefix(, bigEndianSymtab): = truecasebytes.HasPrefix(, littleEndianSymtab): = true = binary.LittleEndian }varintif {iflen() < 8 {return &DecodingError{len(), "unexpected EOF", nil} } = int([7])if != 4 && != 8 {return &DecodingError{7, "invalid pointer size", } } = [8:] }varsym := forlen() >= 4 {varbyteif {// Symbol type, value, Go type. = [0] & 0x3F := [0]&0x40 != 0 := [0]&0x80 != 0if < 26 { += 'A' } else { += 'a' - 26 } .typ = = [1:]if {iflen() < {return &DecodingError{len(), "unexpected EOF", nil} }// fixed-width valueif == 8 { .value = .Uint64([0:8]) = [8:] } else { .value = uint64(.Uint32([0:4])) = [4:] } } else {// varint value .value = 0 := uint(0)forlen() > 0 && [0]&0x80 != 0 { .value |= uint64([0]&0x7F) << += 7 = [1:] }iflen() == 0 {return &DecodingError{len(), "unexpected EOF", nil} } .value |= uint64([0]) << = [1:] }if {iflen() < {return &DecodingError{len(), "unexpected EOF", nil} }// fixed-width go typeif == 8 { .gotype = .Uint64([0:8]) = [8:] } else { .gotype = uint64(.Uint32([0:4])) = [4:] } } } else {// Value, symbol type. .value = uint64(.Uint32([0:4]))iflen() < 5 {return &DecodingError{len(), "unexpected EOF", nil} } = [4]if &0x80 == 0 {return &DecodingError{len() - len() + 4, "bad symbol type", } } &^= 0x80 .typ = = [5:] }// Name.varintvarintfor = 0; < len(); ++ {if [] == 0 { = 1break } }switch {case'z', 'Z': = [+:]for = 0; +2 <= len(); += 2 {if [] == 0 && [+1] == 0 { = 2break } } }iflen() < + {return &DecodingError{len(), "unexpected EOF", nil} } .name = [0:] += = [:]if ! {iflen() < 4 {return &DecodingError{len(), "unexpected EOF", nil} }// Go type. .gotype = uint64(.Uint32([:4])) = [4:] } () }returnnil}// NewTable decodes the Go symbol table (the ".gosymtab" section in ELF),// returning an in-memory representation.// Starting with Go 1.3, the Go symbol table no longer includes symbol data.func ( []byte, *LineTable) (*Table, error) {varint := walksymtab(, func( sym) error { ++returnnil })if != nil {returnnil, }varTableif .isGo12() { .go12line = } := make(map[uint16]string) .Syms = make([]Sym, 0, ) := 0 := 0 := uint8(0) = walksymtab(, func( sym) error { := len(.Syms) .Syms = .Syms[0 : +1] := &.Syms[] .Type = .typ .Value = .value .GoType = .gotype .goVersion = .versionswitch .typ {default:// rewrite name to use . instead of · (c2 b7) := 0 := .namefor := 0; < len(); ++ {if [] == 0xc2 && +1 < len() && [+1] == 0xb7 { ++ [] = '.' } [] = [] ++ } .Name = string(.name[0:])case'z', 'Z':if != 'z' && != 'Z' { ++ }for := 0; < len(.name); += 2 { := binary.BigEndian.Uint16(.name[ : +2]) , := []if ! {return &DecodingError{-1, "bad filename code", } }if := len(.Name); > 0 && .Name[-1] != '/' { .Name += "/" } .Name += } }switch .typ {case'T', 't', 'L', 'l': ++case'f': [uint16(.value)] = .Name } = .typreturnnil })if != nil {returnnil, } .Funcs = make([]Func, 0, ) .Files = make(map[string]*Obj)var *Objif .go12line != nil {// Put all functions into one Obj. .Objs = make([]Obj, 1) = &.Objs[0] .go12line.go12MapFiles(.Files, ) } else { .Objs = make([]Obj, 0, ) }// Count text symbols and attach frame sizes, parameters, and // locals to them. Also, find object file boundaries. := 0for := 0; < len(.Syms); ++ { := &.Syms[]switch .Type {case'Z', 'z': // path symbolif .go12line != nil {// Go 1.2 binaries have the file information elsewhere. Ignore.break }// Finish the current objectif != nil { .Funcs = .Funcs[:] } = len(.Funcs)// Start new object := len(.Objs) .Objs = .Objs[0 : +1] = &.Objs[]// Count & copy path symbolsvarintfor = + 1; < len(.Syms); ++ {if := .Syms[].Type; != 'Z' && != 'z' {break } } .Paths = .Syms[:] = - 1// loop will i++// Record file names := 0for := range .Paths { := &.Paths[]if .Name == "" { -- } else {if == 0 { .Files[.Name] = } ++ } }case'T', 't', 'L', 'l': // text symbolif := len(.Funcs); > 0 { .Funcs[-1].End = .Value }if .Name == "runtime.etext" || .Name == "etext" {continue }// Count parameter and local (auto) symsvar , intvarint :for = + 1; < len(.Syms); ++ {switch .Syms[].Type {case'T', 't', 'L', 'l', 'Z', 'z':breakcase'p': ++case'a': ++ } }// Fill in the function symbol := len(.Funcs) .Funcs = .Funcs[0 : +1] := &.Funcs[] .Func = .Params = make([]*Sym, 0, ) .Locals = make([]*Sym, 0, ) .Sym = .Entry = .Value .Obj = if .go12line != nil {// All functions share the same line table. // It knows how to narrow down to a specific // function quickly. .LineTable = .go12line } elseif != nil { .LineTable = .slice(.Entry) = .LineTable }for := ; < ; ++ { := &.Syms[]switch .Type {case'm': .FrameSize = int(.Value)case'p': := len(.Params) .Params = .Params[0 : +1] .Params[] = case'a': := len(.Locals) .Locals = .Locals[0 : +1] .Locals[] = } } = - 1// loop will i++ } }if .go12line != nil && == 0 { .Funcs = .go12line.go12Funcs() }if != nil { .Funcs = .Funcs[:] }return &, nil}// PCToFunc returns the function containing the program counter pc,// or nil if there is no such function.func ( *Table) ( uint64) *Func { := .Funcsforlen() > 0 { := len() / 2 := &[]switch {case < .Entry: = [0:]case .Entry <= && < .End:returndefault: = [+1:] } }returnnil}// PCToLine looks up line number information for a program counter.// If there is no information, it returns fn == nil.func ( *Table) ( uint64) ( string, int, *Func) {if = .PCToFunc(); == nil {return }if .go12line != nil { = .go12line.go12PCToFile() = .go12line.go12PCToLine() } else { , = .Obj.lineFromAline(.LineTable.PCToLine()) }return}// LineToPC looks up the first program counter on the given line in// the named file. It returns [UnknownFileError] or [UnknownLineError] if// there is an error looking up this line.func ( *Table) ( string, int) ( uint64, *Func, error) { , := .Files[]if ! {return0, nil, UnknownFileError() }if .go12line != nil { := .go12line.go12LineToPC(, )if == 0 {return0, nil, &UnknownLineError{, } }return , .PCToFunc(), nil } , := .alineFromLine(, )if != nil {return }for := range .Funcs { := &.Funcs[] := .LineTable.LineToPC(, .End)if != 0 {return , , nil } }return0, nil, &UnknownLineError{, }}// LookupSym returns the text, data, or bss symbol with the given name,// or nil if no such symbol is found.func ( *Table) ( string) *Sym {// TODO(austin) Maybe make a mapfor := range .Syms { := &.Syms[]switch .Type {case'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':if .Name == {return } } }returnnil}// LookupFunc returns the text, data, or bss symbol with the given name,// or nil if no such symbol is found.func ( *Table) ( string) *Func {for := range .Funcs { := &.Funcs[]if .Sym.Name == {return } }returnnil}// SymByAddr returns the text, data, or bss symbol starting at the given address.func ( *Table) ( uint64) *Sym {for := range .Syms { := &.Syms[]switch .Type {case'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':if .Value == {return } } }returnnil}/* * Object files */// This is legacy code for Go 1.1 and earlier, which used the// Plan 9 format for pc-line tables. This code was never quite// correct. It's probably very close, and it's usually correct, but// we never quite found all the corner cases.//// Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab.func ( *Obj) ( int) (string, int) {typestruct {stringintint * } := &{"", 0, 0, nil} := :for , := range .Paths { := int(.Value)switch {case > :breakcase == 1:// Start a new stack = &{.Name, , 0, }case .Name == "":// Popif == {return"<malformed symbol table>", 0 } .. += - . = .default:// Push = &{.Name, , 0, } } }if == {return"", 0 }return ., - . - . + 1}func ( *Obj) ( string, int) (int, error) {if < 1 {return0, &UnknownLineError{, } }for , := range .Paths {// Find this pathif .Name != {continue }// Find this line at this stack level := 0varint += int(.Value) :for , := range .Paths[:] { := int(.Value)switch {case == 1 && >= :return - 1, nilcase .Name == "": --if == 0 {break } elseif == 1 { += - }default:if == 1 { = } ++ } }return0, &UnknownLineError{, } }return0, UnknownFileError()}/* * Errors */// UnknownFileError represents a failure to find the specific file in// the symbol table.typeUnknownFileErrorstringfunc ( UnknownFileError) () string { return"unknown file: " + string() }// UnknownLineError represents a failure to map a line to a program// counter, either because the line is beyond the bounds of the file// or because there is no code on the given line.typeUnknownLineErrorstruct { File string Line int}func ( *UnknownLineError) () string {return"no code at " + .File + ":" + strconv.Itoa(.Line)}// DecodingError represents an error during the decoding of// the symbol table.typeDecodingErrorstruct { off int msg string val any}func ( *DecodingError) () string { := .msgif .val != nil { += fmt.Sprintf(" '%v'", .val) } += fmt.Sprintf(" at byte %#x", .off)return}
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.