// 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 gosym import ( ) /* * Symbols */ // A Sym represents a single symbol table entry. type Sym struct { 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. type Func struct { 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. type Obj struct { // 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. type Table struct { 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 { if len() == 0 { // missing symtab is okay return nil } var binary.ByteOrder = binary.BigEndian := false switch { case bytes.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.LittleEndian case bytes.HasPrefix(, bigEndianSymtab): = true case bytes.HasPrefix(, littleEndianSymtab): = true = binary.LittleEndian } var int if { if len() < 8 { return &DecodingError{len(), "unexpected EOF", nil} } = int([7]) if != 4 && != 8 { return &DecodingError{7, "invalid pointer size", } } = [8:] } var sym := for len() >= 4 { var byte if { // Symbol type, value, Go type. = [0] & 0x3F := [0]&0x40 != 0 := [0]&0x80 != 0 if < 26 { += 'A' } else { += 'a' - 26 } .typ = = [1:] if { if len() < { return &DecodingError{len(), "unexpected EOF", nil} } // fixed-width value if == 8 { .value = .Uint64([0:8]) = [8:] } else { .value = uint64(.Uint32([0:4])) = [4:] } } else { // varint value .value = 0 := uint(0) for len() > 0 && [0]&0x80 != 0 { .value |= uint64([0]&0x7F) << += 7 = [1:] } if len() == 0 { return &DecodingError{len(), "unexpected EOF", nil} } .value |= uint64([0]) << = [1:] } if { if len() < { return &DecodingError{len(), "unexpected EOF", nil} } // fixed-width go type if == 8 { .gotype = .Uint64([0:8]) = [8:] } else { .gotype = uint64(.Uint32([0:4])) = [4:] } } } else { // Value, symbol type. .value = uint64(.Uint32([0:4])) if len() < 5 { return &DecodingError{len(), "unexpected EOF", nil} } = [4] if &0x80 == 0 { return &DecodingError{len() - len() + 4, "bad symbol type", } } &^= 0x80 .typ = = [5:] } // Name. var int var int for = 0; < len(); ++ { if [] == 0 { = 1 break } } switch { case 'z', 'Z': = [+:] for = 0; +2 <= len(); += 2 { if [] == 0 && [+1] == 0 { = 2 break } } } if len() < + { return &DecodingError{len(), "unexpected EOF", nil} } .name = [0:] += = [:] if ! { if len() < 4 { return &DecodingError{len(), "unexpected EOF", nil} } // Go type. .gotype = uint64(.Uint32([:4])) = [4:] } () } return nil } // 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) { var int := walksymtab(, func( sym) error { ++ return nil }) if != nil { return nil, } var Table if .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 = .version switch .typ { default: // rewrite name to use . instead of · (c2 b7) := 0 := .name for := 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 } = .typ return nil }) if != nil { return nil, } .Funcs = make([]Func, 0, ) .Files = make(map[string]*Obj) var *Obj if .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. := 0 for := 0; < len(.Syms); ++ { := &.Syms[] switch .Type { case 'Z', 'z': // path symbol if .go12line != nil { // Go 1.2 binaries have the file information elsewhere. Ignore. break } // Finish the current object if != nil { .Funcs = .Funcs[:] } = len(.Funcs) // Start new object := len(.Objs) .Objs = .Objs[0 : +1] = &.Objs[] // Count & copy path symbols var int for = + 1; < len(.Syms); ++ { if := .Syms[].Type; != 'Z' && != 'z' { break } } .Paths = .Syms[:] = - 1 // loop will i++ // Record file names := 0 for := range .Paths { := &.Paths[] if .Name == "" { -- } else { if == 0 { .Files[.Name] = } ++ } } case 'T', 't', 'L', 'l': // text symbol if := len(.Funcs); > 0 { .Funcs[-1].End = .Value } if .Name == "runtime.etext" || .Name == "etext" { continue } // Count parameter and local (auto) syms var , int var int : for = + 1; < len(.Syms); ++ { switch .Syms[].Type { case 'T', 't', 'L', 'l', 'Z', 'z': break case '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 } else if != 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 { := .Funcs for len() > 0 { := len() / 2 := &[] switch { case < .Entry: = [0:] case .Entry <= && < .End: return default: = [+1:] } } return nil } // 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 ! { return 0, nil, UnknownFileError() } if .go12line != nil { := .go12line.go12LineToPC(, ) if == 0 { return 0, nil, &UnknownLineError{, } } return , .PCToFunc(), nil } , := .alineFromLine(, ) if != nil { return } for := range .Funcs { := &.Funcs[] := .LineTable.LineToPC(, .End) if != 0 { return , , nil } } return 0, 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 map for := range .Syms { := &.Syms[] switch .Type { case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': if .Name == { return } } } return nil } // 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 } } return nil } // 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 } } } return nil } /* * 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) { type struct { string int int * } := &{"", 0, 0, nil} := : for , := range .Paths { := int(.Value) switch { case > : break case == 1: // Start a new stack = &{.Name, , 0, } case .Name == "": // Pop if == { return "<malformed symbol table>", 0 } .. += - . = . default: // Push = &{.Name, , 0, } } } if == { return "", 0 } return ., - . - . + 1 } func ( *Obj) ( string, int) (int, error) { if < 1 { return 0, &UnknownLineError{, } } for , := range .Paths { // Find this path if .Name != { continue } // Find this line at this stack level := 0 var int += int(.Value) : for , := range .Paths[:] { := int(.Value) switch { case == 1 && >= : return - 1, nil case .Name == "": -- if == 0 { break } else if == 1 { += - } default: if == 1 { = } ++ } } return 0, &UnknownLineError{, } } return 0, UnknownFileError() } /* * Errors */ // UnknownFileError represents a failure to find the specific file in // the symbol table. type UnknownFileError string func ( 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. type UnknownLineError struct { 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. type DecodingError struct { off int msg string val any } func ( *DecodingError) () string { := .msg if .val != nil { += fmt.Sprintf(" '%v'", .val) } += fmt.Sprintf(" at byte %#x", .off) return }