...
Run Format

Source file src/debug/gosym/symtab.go

Documentation: debug/gosym

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package gosym implements access to the Go symbol
     6  // and line number tables embedded in Go binaries generated
     7  // by the gc compilers.
     8  package gosym
     9  
    10  // The table format is a variant of the format used in Plan 9's a.out
    11  // format, documented at https://9p.io/magic/man2html/6/a.out.
    12  // The best reference for the differences between the Plan 9 format
    13  // and the Go format is the runtime source, specifically ../../runtime/symtab.c.
    14  
    15  import (
    16  	"bytes"
    17  	"encoding/binary"
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  )
    22  
    23  /*
    24   * Symbols
    25   */
    26  
    27  // A Sym represents a single symbol table entry.
    28  type Sym struct {
    29  	Value  uint64
    30  	Type   byte
    31  	Name   string
    32  	GoType uint64
    33  	// If this symbol is a function symbol, the corresponding Func
    34  	Func *Func
    35  }
    36  
    37  // Static reports whether this symbol is static (not visible outside its file).
    38  func (s *Sym) Static() bool { return s.Type >= 'a' }
    39  
    40  // PackageName returns the package part of the symbol name,
    41  // or the empty string if there is none.
    42  func (s *Sym) PackageName() string {
    43  	pathend := strings.LastIndex(s.Name, "/")
    44  	if pathend < 0 {
    45  		pathend = 0
    46  	}
    47  
    48  	if i := strings.Index(s.Name[pathend:], "."); i != -1 {
    49  		return s.Name[:pathend+i]
    50  	}
    51  	return ""
    52  }
    53  
    54  // ReceiverName returns the receiver type name of this symbol,
    55  // or the empty string if there is none.
    56  func (s *Sym) ReceiverName() string {
    57  	pathend := strings.LastIndex(s.Name, "/")
    58  	if pathend < 0 {
    59  		pathend = 0
    60  	}
    61  	l := strings.Index(s.Name[pathend:], ".")
    62  	r := strings.LastIndex(s.Name[pathend:], ".")
    63  	if l == -1 || r == -1 || l == r {
    64  		return ""
    65  	}
    66  	return s.Name[pathend+l+1 : pathend+r]
    67  }
    68  
    69  // BaseName returns the symbol name without the package or receiver name.
    70  func (s *Sym) BaseName() string {
    71  	if i := strings.LastIndex(s.Name, "."); i != -1 {
    72  		return s.Name[i+1:]
    73  	}
    74  	return s.Name
    75  }
    76  
    77  // A Func collects information about a single function.
    78  type Func struct {
    79  	Entry uint64
    80  	*Sym
    81  	End       uint64
    82  	Params    []*Sym // nil for Go 1.3 and later binaries
    83  	Locals    []*Sym // nil for Go 1.3 and later binaries
    84  	FrameSize int
    85  	LineTable *LineTable
    86  	Obj       *Obj
    87  }
    88  
    89  // An Obj represents a collection of functions in a symbol table.
    90  //
    91  // The exact method of division of a binary into separate Objs is an internal detail
    92  // of the symbol table format.
    93  //
    94  // In early versions of Go each source file became a different Obj.
    95  //
    96  // In Go 1 and Go 1.1, each package produced one Obj for all Go sources
    97  // and one Obj per C source file.
    98  //
    99  // In Go 1.2, there is a single Obj for the entire program.
   100  type Obj struct {
   101  	// Funcs is a list of functions in the Obj.
   102  	Funcs []Func
   103  
   104  	// In Go 1.1 and earlier, Paths is a list of symbols corresponding
   105  	// to the source file names that produced the Obj.
   106  	// In Go 1.2, Paths is nil.
   107  	// Use the keys of Table.Files to obtain a list of source files.
   108  	Paths []Sym // meta
   109  }
   110  
   111  /*
   112   * Symbol tables
   113   */
   114  
   115  // Table represents a Go symbol table. It stores all of the
   116  // symbols decoded from the program and provides methods to translate
   117  // between symbols, names, and addresses.
   118  type Table struct {
   119  	Syms  []Sym // nil for Go 1.3 and later binaries
   120  	Funcs []Func
   121  	Files map[string]*Obj // nil for Go 1.2 and later binaries
   122  	Objs  []Obj           // nil for Go 1.2 and later binaries
   123  
   124  	go12line *LineTable // Go 1.2 line number table
   125  }
   126  
   127  type sym struct {
   128  	value  uint64
   129  	gotype uint64
   130  	typ    byte
   131  	name   []byte
   132  }
   133  
   134  var (
   135  	littleEndianSymtab    = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
   136  	bigEndianSymtab       = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
   137  	oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
   138  )
   139  
   140  func walksymtab(data []byte, fn func(sym) error) error {
   141  	if len(data) == 0 { // missing symtab is okay
   142  		return nil
   143  	}
   144  	var order binary.ByteOrder = binary.BigEndian
   145  	newTable := false
   146  	switch {
   147  	case bytes.HasPrefix(data, oldLittleEndianSymtab):
   148  		// Same as Go 1.0, but little endian.
   149  		// Format was used during interim development between Go 1.0 and Go 1.1.
   150  		// Should not be widespread, but easy to support.
   151  		data = data[6:]
   152  		order = binary.LittleEndian
   153  	case bytes.HasPrefix(data, bigEndianSymtab):
   154  		newTable = true
   155  	case bytes.HasPrefix(data, littleEndianSymtab):
   156  		newTable = true
   157  		order = binary.LittleEndian
   158  	}
   159  	var ptrsz int
   160  	if newTable {
   161  		if len(data) < 8 {
   162  			return &DecodingError{len(data), "unexpected EOF", nil}
   163  		}
   164  		ptrsz = int(data[7])
   165  		if ptrsz != 4 && ptrsz != 8 {
   166  			return &DecodingError{7, "invalid pointer size", ptrsz}
   167  		}
   168  		data = data[8:]
   169  	}
   170  	var s sym
   171  	p := data
   172  	for len(p) >= 4 {
   173  		var typ byte
   174  		if newTable {
   175  			// Symbol type, value, Go type.
   176  			typ = p[0] & 0x3F
   177  			wideValue := p[0]&0x40 != 0
   178  			goType := p[0]&0x80 != 0
   179  			if typ < 26 {
   180  				typ += 'A'
   181  			} else {
   182  				typ += 'a' - 26
   183  			}
   184  			s.typ = typ
   185  			p = p[1:]
   186  			if wideValue {
   187  				if len(p) < ptrsz {
   188  					return &DecodingError{len(data), "unexpected EOF", nil}
   189  				}
   190  				// fixed-width value
   191  				if ptrsz == 8 {
   192  					s.value = order.Uint64(p[0:8])
   193  					p = p[8:]
   194  				} else {
   195  					s.value = uint64(order.Uint32(p[0:4]))
   196  					p = p[4:]
   197  				}
   198  			} else {
   199  				// varint value
   200  				s.value = 0
   201  				shift := uint(0)
   202  				for len(p) > 0 && p[0]&0x80 != 0 {
   203  					s.value |= uint64(p[0]&0x7F) << shift
   204  					shift += 7
   205  					p = p[1:]
   206  				}
   207  				if len(p) == 0 {
   208  					return &DecodingError{len(data), "unexpected EOF", nil}
   209  				}
   210  				s.value |= uint64(p[0]) << shift
   211  				p = p[1:]
   212  			}
   213  			if goType {
   214  				if len(p) < ptrsz {
   215  					return &DecodingError{len(data), "unexpected EOF", nil}
   216  				}
   217  				// fixed-width go type
   218  				if ptrsz == 8 {
   219  					s.gotype = order.Uint64(p[0:8])
   220  					p = p[8:]
   221  				} else {
   222  					s.gotype = uint64(order.Uint32(p[0:4]))
   223  					p = p[4:]
   224  				}
   225  			}
   226  		} else {
   227  			// Value, symbol type.
   228  			s.value = uint64(order.Uint32(p[0:4]))
   229  			if len(p) < 5 {
   230  				return &DecodingError{len(data), "unexpected EOF", nil}
   231  			}
   232  			typ = p[4]
   233  			if typ&0x80 == 0 {
   234  				return &DecodingError{len(data) - len(p) + 4, "bad symbol type", typ}
   235  			}
   236  			typ &^= 0x80
   237  			s.typ = typ
   238  			p = p[5:]
   239  		}
   240  
   241  		// Name.
   242  		var i int
   243  		var nnul int
   244  		for i = 0; i < len(p); i++ {
   245  			if p[i] == 0 {
   246  				nnul = 1
   247  				break
   248  			}
   249  		}
   250  		switch typ {
   251  		case 'z', 'Z':
   252  			p = p[i+nnul:]
   253  			for i = 0; i+2 <= len(p); i += 2 {
   254  				if p[i] == 0 && p[i+1] == 0 {
   255  					nnul = 2
   256  					break
   257  				}
   258  			}
   259  		}
   260  		if len(p) < i+nnul {
   261  			return &DecodingError{len(data), "unexpected EOF", nil}
   262  		}
   263  		s.name = p[0:i]
   264  		i += nnul
   265  		p = p[i:]
   266  
   267  		if !newTable {
   268  			if len(p) < 4 {
   269  				return &DecodingError{len(data), "unexpected EOF", nil}
   270  			}
   271  			// Go type.
   272  			s.gotype = uint64(order.Uint32(p[:4]))
   273  			p = p[4:]
   274  		}
   275  		fn(s)
   276  	}
   277  	return nil
   278  }
   279  
   280  // NewTable decodes the Go symbol table (the ".gosymtab" section in ELF),
   281  // returning an in-memory representation.
   282  // Starting with Go 1.3, the Go symbol table no longer includes symbol data.
   283  func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
   284  	var n int
   285  	err := walksymtab(symtab, func(s sym) error {
   286  		n++
   287  		return nil
   288  	})
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	var t Table
   294  	if pcln.isGo12() {
   295  		t.go12line = pcln
   296  	}
   297  	fname := make(map[uint16]string)
   298  	t.Syms = make([]Sym, 0, n)
   299  	nf := 0
   300  	nz := 0
   301  	lasttyp := uint8(0)
   302  	err = walksymtab(symtab, func(s sym) error {
   303  		n := len(t.Syms)
   304  		t.Syms = t.Syms[0 : n+1]
   305  		ts := &t.Syms[n]
   306  		ts.Type = s.typ
   307  		ts.Value = s.value
   308  		ts.GoType = s.gotype
   309  		switch s.typ {
   310  		default:
   311  			// rewrite name to use . instead of · (c2 b7)
   312  			w := 0
   313  			b := s.name
   314  			for i := 0; i < len(b); i++ {
   315  				if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 {
   316  					i++
   317  					b[i] = '.'
   318  				}
   319  				b[w] = b[i]
   320  				w++
   321  			}
   322  			ts.Name = string(s.name[0:w])
   323  		case 'z', 'Z':
   324  			if lasttyp != 'z' && lasttyp != 'Z' {
   325  				nz++
   326  			}
   327  			for i := 0; i < len(s.name); i += 2 {
   328  				eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
   329  				elt, ok := fname[eltIdx]
   330  				if !ok {
   331  					return &DecodingError{-1, "bad filename code", eltIdx}
   332  				}
   333  				if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
   334  					ts.Name += "/"
   335  				}
   336  				ts.Name += elt
   337  			}
   338  		}
   339  		switch s.typ {
   340  		case 'T', 't', 'L', 'l':
   341  			nf++
   342  		case 'f':
   343  			fname[uint16(s.value)] = ts.Name
   344  		}
   345  		lasttyp = s.typ
   346  		return nil
   347  	})
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	t.Funcs = make([]Func, 0, nf)
   353  	t.Files = make(map[string]*Obj)
   354  
   355  	var obj *Obj
   356  	if t.go12line != nil {
   357  		// Put all functions into one Obj.
   358  		t.Objs = make([]Obj, 1)
   359  		obj = &t.Objs[0]
   360  		t.go12line.go12MapFiles(t.Files, obj)
   361  	} else {
   362  		t.Objs = make([]Obj, 0, nz)
   363  	}
   364  
   365  	// Count text symbols and attach frame sizes, parameters, and
   366  	// locals to them. Also, find object file boundaries.
   367  	lastf := 0
   368  	for i := 0; i < len(t.Syms); i++ {
   369  		sym := &t.Syms[i]
   370  		switch sym.Type {
   371  		case 'Z', 'z': // path symbol
   372  			if t.go12line != nil {
   373  				// Go 1.2 binaries have the file information elsewhere. Ignore.
   374  				break
   375  			}
   376  			// Finish the current object
   377  			if obj != nil {
   378  				obj.Funcs = t.Funcs[lastf:]
   379  			}
   380  			lastf = len(t.Funcs)
   381  
   382  			// Start new object
   383  			n := len(t.Objs)
   384  			t.Objs = t.Objs[0 : n+1]
   385  			obj = &t.Objs[n]
   386  
   387  			// Count & copy path symbols
   388  			var end int
   389  			for end = i + 1; end < len(t.Syms); end++ {
   390  				if c := t.Syms[end].Type; c != 'Z' && c != 'z' {
   391  					break
   392  				}
   393  			}
   394  			obj.Paths = t.Syms[i:end]
   395  			i = end - 1 // loop will i++
   396  
   397  			// Record file names
   398  			depth := 0
   399  			for j := range obj.Paths {
   400  				s := &obj.Paths[j]
   401  				if s.Name == "" {
   402  					depth--
   403  				} else {
   404  					if depth == 0 {
   405  						t.Files[s.Name] = obj
   406  					}
   407  					depth++
   408  				}
   409  			}
   410  
   411  		case 'T', 't', 'L', 'l': // text symbol
   412  			if n := len(t.Funcs); n > 0 {
   413  				t.Funcs[n-1].End = sym.Value
   414  			}
   415  			if sym.Name == "runtime.etext" || sym.Name == "etext" {
   416  				continue
   417  			}
   418  
   419  			// Count parameter and local (auto) syms
   420  			var np, na int
   421  			var end int
   422  		countloop:
   423  			for end = i + 1; end < len(t.Syms); end++ {
   424  				switch t.Syms[end].Type {
   425  				case 'T', 't', 'L', 'l', 'Z', 'z':
   426  					break countloop
   427  				case 'p':
   428  					np++
   429  				case 'a':
   430  					na++
   431  				}
   432  			}
   433  
   434  			// Fill in the function symbol
   435  			n := len(t.Funcs)
   436  			t.Funcs = t.Funcs[0 : n+1]
   437  			fn := &t.Funcs[n]
   438  			sym.Func = fn
   439  			fn.Params = make([]*Sym, 0, np)
   440  			fn.Locals = make([]*Sym, 0, na)
   441  			fn.Sym = sym
   442  			fn.Entry = sym.Value
   443  			fn.Obj = obj
   444  			if t.go12line != nil {
   445  				// All functions share the same line table.
   446  				// It knows how to narrow down to a specific
   447  				// function quickly.
   448  				fn.LineTable = t.go12line
   449  			} else if pcln != nil {
   450  				fn.LineTable = pcln.slice(fn.Entry)
   451  				pcln = fn.LineTable
   452  			}
   453  			for j := i; j < end; j++ {
   454  				s := &t.Syms[j]
   455  				switch s.Type {
   456  				case 'm':
   457  					fn.FrameSize = int(s.Value)
   458  				case 'p':
   459  					n := len(fn.Params)
   460  					fn.Params = fn.Params[0 : n+1]
   461  					fn.Params[n] = s
   462  				case 'a':
   463  					n := len(fn.Locals)
   464  					fn.Locals = fn.Locals[0 : n+1]
   465  					fn.Locals[n] = s
   466  				}
   467  			}
   468  			i = end - 1 // loop will i++
   469  		}
   470  	}
   471  
   472  	if t.go12line != nil && nf == 0 {
   473  		t.Funcs = t.go12line.go12Funcs()
   474  	}
   475  	if obj != nil {
   476  		obj.Funcs = t.Funcs[lastf:]
   477  	}
   478  	return &t, nil
   479  }
   480  
   481  // PCToFunc returns the function containing the program counter pc,
   482  // or nil if there is no such function.
   483  func (t *Table) PCToFunc(pc uint64) *Func {
   484  	funcs := t.Funcs
   485  	for len(funcs) > 0 {
   486  		m := len(funcs) / 2
   487  		fn := &funcs[m]
   488  		switch {
   489  		case pc < fn.Entry:
   490  			funcs = funcs[0:m]
   491  		case fn.Entry <= pc && pc < fn.End:
   492  			return fn
   493  		default:
   494  			funcs = funcs[m+1:]
   495  		}
   496  	}
   497  	return nil
   498  }
   499  
   500  // PCToLine looks up line number information for a program counter.
   501  // If there is no information, it returns fn == nil.
   502  func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) {
   503  	if fn = t.PCToFunc(pc); fn == nil {
   504  		return
   505  	}
   506  	if t.go12line != nil {
   507  		file = t.go12line.go12PCToFile(pc)
   508  		line = t.go12line.go12PCToLine(pc)
   509  	} else {
   510  		file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))
   511  	}
   512  	return
   513  }
   514  
   515  // LineToPC looks up the first program counter on the given line in
   516  // the named file. It returns UnknownPathError or UnknownLineError if
   517  // there is an error looking up this line.
   518  func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
   519  	obj, ok := t.Files[file]
   520  	if !ok {
   521  		return 0, nil, UnknownFileError(file)
   522  	}
   523  
   524  	if t.go12line != nil {
   525  		pc := t.go12line.go12LineToPC(file, line)
   526  		if pc == 0 {
   527  			return 0, nil, &UnknownLineError{file, line}
   528  		}
   529  		return pc, t.PCToFunc(pc), nil
   530  	}
   531  
   532  	abs, err := obj.alineFromLine(file, line)
   533  	if err != nil {
   534  		return
   535  	}
   536  	for i := range obj.Funcs {
   537  		f := &obj.Funcs[i]
   538  		pc := f.LineTable.LineToPC(abs, f.End)
   539  		if pc != 0 {
   540  			return pc, f, nil
   541  		}
   542  	}
   543  	return 0, nil, &UnknownLineError{file, line}
   544  }
   545  
   546  // LookupSym returns the text, data, or bss symbol with the given name,
   547  // or nil if no such symbol is found.
   548  func (t *Table) LookupSym(name string) *Sym {
   549  	// TODO(austin) Maybe make a map
   550  	for i := range t.Syms {
   551  		s := &t.Syms[i]
   552  		switch s.Type {
   553  		case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
   554  			if s.Name == name {
   555  				return s
   556  			}
   557  		}
   558  	}
   559  	return nil
   560  }
   561  
   562  // LookupFunc returns the text, data, or bss symbol with the given name,
   563  // or nil if no such symbol is found.
   564  func (t *Table) LookupFunc(name string) *Func {
   565  	for i := range t.Funcs {
   566  		f := &t.Funcs[i]
   567  		if f.Sym.Name == name {
   568  			return f
   569  		}
   570  	}
   571  	return nil
   572  }
   573  
   574  // SymByAddr returns the text, data, or bss symbol starting at the given address.
   575  func (t *Table) SymByAddr(addr uint64) *Sym {
   576  	for i := range t.Syms {
   577  		s := &t.Syms[i]
   578  		switch s.Type {
   579  		case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
   580  			if s.Value == addr {
   581  				return s
   582  			}
   583  		}
   584  	}
   585  	return nil
   586  }
   587  
   588  /*
   589   * Object files
   590   */
   591  
   592  // This is legacy code for Go 1.1 and earlier, which used the
   593  // Plan 9 format for pc-line tables. This code was never quite
   594  // correct. It's probably very close, and it's usually correct, but
   595  // we never quite found all the corner cases.
   596  //
   597  // Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab.
   598  
   599  func (o *Obj) lineFromAline(aline int) (string, int) {
   600  	type stackEnt struct {
   601  		path   string
   602  		start  int
   603  		offset int
   604  		prev   *stackEnt
   605  	}
   606  
   607  	noPath := &stackEnt{"", 0, 0, nil}
   608  	tos := noPath
   609  
   610  pathloop:
   611  	for _, s := range o.Paths {
   612  		val := int(s.Value)
   613  		switch {
   614  		case val > aline:
   615  			break pathloop
   616  
   617  		case val == 1:
   618  			// Start a new stack
   619  			tos = &stackEnt{s.Name, val, 0, noPath}
   620  
   621  		case s.Name == "":
   622  			// Pop
   623  			if tos == noPath {
   624  				return "<malformed symbol table>", 0
   625  			}
   626  			tos.prev.offset += val - tos.start
   627  			tos = tos.prev
   628  
   629  		default:
   630  			// Push
   631  			tos = &stackEnt{s.Name, val, 0, tos}
   632  		}
   633  	}
   634  
   635  	if tos == noPath {
   636  		return "", 0
   637  	}
   638  	return tos.path, aline - tos.start - tos.offset + 1
   639  }
   640  
   641  func (o *Obj) alineFromLine(path string, line int) (int, error) {
   642  	if line < 1 {
   643  		return 0, &UnknownLineError{path, line}
   644  	}
   645  
   646  	for i, s := range o.Paths {
   647  		// Find this path
   648  		if s.Name != path {
   649  			continue
   650  		}
   651  
   652  		// Find this line at this stack level
   653  		depth := 0
   654  		var incstart int
   655  		line += int(s.Value)
   656  	pathloop:
   657  		for _, s := range o.Paths[i:] {
   658  			val := int(s.Value)
   659  			switch {
   660  			case depth == 1 && val >= line:
   661  				return line - 1, nil
   662  
   663  			case s.Name == "":
   664  				depth--
   665  				if depth == 0 {
   666  					break pathloop
   667  				} else if depth == 1 {
   668  					line += val - incstart
   669  				}
   670  
   671  			default:
   672  				if depth == 1 {
   673  					incstart = val
   674  				}
   675  				depth++
   676  			}
   677  		}
   678  		return 0, &UnknownLineError{path, line}
   679  	}
   680  	return 0, UnknownFileError(path)
   681  }
   682  
   683  /*
   684   * Errors
   685   */
   686  
   687  // UnknownFileError represents a failure to find the specific file in
   688  // the symbol table.
   689  type UnknownFileError string
   690  
   691  func (e UnknownFileError) Error() string { return "unknown file: " + string(e) }
   692  
   693  // UnknownLineError represents a failure to map a line to a program
   694  // counter, either because the line is beyond the bounds of the file
   695  // or because there is no code on the given line.
   696  type UnknownLineError struct {
   697  	File string
   698  	Line int
   699  }
   700  
   701  func (e *UnknownLineError) Error() string {
   702  	return "no code at " + e.File + ":" + strconv.Itoa(e.Line)
   703  }
   704  
   705  // DecodingError represents an error during the decoding of
   706  // the symbol table.
   707  type DecodingError struct {
   708  	off int
   709  	msg string
   710  	val interface{}
   711  }
   712  
   713  func (e *DecodingError) Error() string {
   714  	msg := e.msg
   715  	if e.val != nil {
   716  		msg += fmt.Sprintf(" '%v'", e.val)
   717  	}
   718  	msg += fmt.Sprintf(" at byte %#x", e.off)
   719  	return msg
   720  }
   721  

View as plain text