forked from lug/matterbridge
		
	
		
			
				
	
	
		
			715 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			715 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2013 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 goobj implements reading of Go object files and archives.
 | |
| //
 | |
| // TODO(rsc): Decide where this package should live. (golang.org/issue/6932)
 | |
| // TODO(rsc): Decide the appropriate integer types for various fields.
 | |
| // TODO(rsc): Write tests. (File format still up in the air a little.)
 | |
| package goobj
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/google/gops/internal/obj"
 | |
| )
 | |
| 
 | |
| // A SymKind describes the kind of memory represented by a symbol.
 | |
| type SymKind int
 | |
| 
 | |
| // This list is taken from include/link.h.
 | |
| 
 | |
| // Defined SymKind values.
 | |
| // TODO(rsc): Give idiomatic Go names.
 | |
| // TODO(rsc): Reduce the number of symbol types in the object files.
 | |
| const (
 | |
| 	// readonly, executable
 | |
| 	STEXT      = SymKind(obj.STEXT)
 | |
| 	SELFRXSECT = SymKind(obj.SELFRXSECT)
 | |
| 
 | |
| 	// readonly, non-executable
 | |
| 	STYPE      = SymKind(obj.STYPE)
 | |
| 	SSTRING    = SymKind(obj.SSTRING)
 | |
| 	SGOSTRING  = SymKind(obj.SGOSTRING)
 | |
| 	SGOFUNC    = SymKind(obj.SGOFUNC)
 | |
| 	SRODATA    = SymKind(obj.SRODATA)
 | |
| 	SFUNCTAB   = SymKind(obj.SFUNCTAB)
 | |
| 	STYPELINK  = SymKind(obj.STYPELINK)
 | |
| 	SITABLINK  = SymKind(obj.SITABLINK)
 | |
| 	SSYMTAB    = SymKind(obj.SSYMTAB) // TODO: move to unmapped section
 | |
| 	SPCLNTAB   = SymKind(obj.SPCLNTAB)
 | |
| 	SELFROSECT = SymKind(obj.SELFROSECT)
 | |
| 
 | |
| 	// writable, non-executable
 | |
| 	SMACHOPLT  = SymKind(obj.SMACHOPLT)
 | |
| 	SELFSECT   = SymKind(obj.SELFSECT)
 | |
| 	SMACHO     = SymKind(obj.SMACHO) // Mach-O __nl_symbol_ptr
 | |
| 	SMACHOGOT  = SymKind(obj.SMACHOGOT)
 | |
| 	SWINDOWS   = SymKind(obj.SWINDOWS)
 | |
| 	SELFGOT    = SymKind(obj.SELFGOT)
 | |
| 	SNOPTRDATA = SymKind(obj.SNOPTRDATA)
 | |
| 	SINITARR   = SymKind(obj.SINITARR)
 | |
| 	SDATA      = SymKind(obj.SDATA)
 | |
| 	SBSS       = SymKind(obj.SBSS)
 | |
| 	SNOPTRBSS  = SymKind(obj.SNOPTRBSS)
 | |
| 	STLSBSS    = SymKind(obj.STLSBSS)
 | |
| 
 | |
| 	// not mapped
 | |
| 	SXREF             = SymKind(obj.SXREF)
 | |
| 	SMACHOSYMSTR      = SymKind(obj.SMACHOSYMSTR)
 | |
| 	SMACHOSYMTAB      = SymKind(obj.SMACHOSYMTAB)
 | |
| 	SMACHOINDIRECTPLT = SymKind(obj.SMACHOINDIRECTPLT)
 | |
| 	SMACHOINDIRECTGOT = SymKind(obj.SMACHOINDIRECTGOT)
 | |
| 	SFILE             = SymKind(obj.SFILE)
 | |
| 	SFILEPATH         = SymKind(obj.SFILEPATH)
 | |
| 	SCONST            = SymKind(obj.SCONST)
 | |
| 	SDYNIMPORT        = SymKind(obj.SDYNIMPORT)
 | |
| 	SHOSTOBJ          = SymKind(obj.SHOSTOBJ)
 | |
| )
 | |
| 
 | |
| var symKindStrings = []string{
 | |
| 	SBSS:              "SBSS",
 | |
| 	SCONST:            "SCONST",
 | |
| 	SDATA:             "SDATA",
 | |
| 	SDYNIMPORT:        "SDYNIMPORT",
 | |
| 	SELFROSECT:        "SELFROSECT",
 | |
| 	SELFRXSECT:        "SELFRXSECT",
 | |
| 	SELFSECT:          "SELFSECT",
 | |
| 	SFILE:             "SFILE",
 | |
| 	SFILEPATH:         "SFILEPATH",
 | |
| 	SFUNCTAB:          "SFUNCTAB",
 | |
| 	SGOFUNC:           "SGOFUNC",
 | |
| 	SGOSTRING:         "SGOSTRING",
 | |
| 	SHOSTOBJ:          "SHOSTOBJ",
 | |
| 	SINITARR:          "SINITARR",
 | |
| 	SMACHO:            "SMACHO",
 | |
| 	SMACHOGOT:         "SMACHOGOT",
 | |
| 	SMACHOINDIRECTGOT: "SMACHOINDIRECTGOT",
 | |
| 	SMACHOINDIRECTPLT: "SMACHOINDIRECTPLT",
 | |
| 	SMACHOPLT:         "SMACHOPLT",
 | |
| 	SMACHOSYMSTR:      "SMACHOSYMSTR",
 | |
| 	SMACHOSYMTAB:      "SMACHOSYMTAB",
 | |
| 	SNOPTRBSS:         "SNOPTRBSS",
 | |
| 	SNOPTRDATA:        "SNOPTRDATA",
 | |
| 	SPCLNTAB:          "SPCLNTAB",
 | |
| 	SRODATA:           "SRODATA",
 | |
| 	SSTRING:           "SSTRING",
 | |
| 	SSYMTAB:           "SSYMTAB",
 | |
| 	STEXT:             "STEXT",
 | |
| 	STLSBSS:           "STLSBSS",
 | |
| 	STYPE:             "STYPE",
 | |
| 	STYPELINK:         "STYPELINK",
 | |
| 	SITABLINK:         "SITABLINK",
 | |
| 	SWINDOWS:          "SWINDOWS",
 | |
| 	SXREF:             "SXREF",
 | |
| }
 | |
| 
 | |
| func (k SymKind) String() string {
 | |
| 	if k < 0 || int(k) >= len(symKindStrings) {
 | |
| 		return fmt.Sprintf("SymKind(%d)", k)
 | |
| 	}
 | |
| 	return symKindStrings[k]
 | |
| }
 | |
| 
 | |
| // A Sym is a named symbol in an object file.
 | |
| type Sym struct {
 | |
| 	SymID         // symbol identifier (name and version)
 | |
| 	Kind  SymKind // kind of symbol
 | |
| 	DupOK bool    // are duplicate definitions okay?
 | |
| 	Size  int     // size of corresponding data
 | |
| 	Type  SymID   // symbol for Go type information
 | |
| 	Data  Data    // memory image of symbol
 | |
| 	Reloc []Reloc // relocations to apply to Data
 | |
| 	Func  *Func   // additional data for functions
 | |
| }
 | |
| 
 | |
| // A SymID - the combination of Name and Version - uniquely identifies
 | |
| // a symbol within a package.
 | |
| type SymID struct {
 | |
| 	// Name is the name of a symbol.
 | |
| 	Name string
 | |
| 
 | |
| 	// Version is zero for symbols with global visibility.
 | |
| 	// Symbols with only file visibility (such as file-level static
 | |
| 	// declarations in C) have a non-zero version distinguishing
 | |
| 	// a symbol in one file from a symbol of the same name
 | |
| 	// in another file
 | |
| 	Version int
 | |
| }
 | |
| 
 | |
| func (s SymID) String() string {
 | |
| 	if s.Version == 0 {
 | |
| 		return s.Name
 | |
| 	}
 | |
| 	return fmt.Sprintf("%s<%d>", s.Name, s.Version)
 | |
| }
 | |
| 
 | |
| // A Data is a reference to data stored in an object file.
 | |
| // It records the offset and size of the data, so that a client can
 | |
| // read the data only if necessary.
 | |
| type Data struct {
 | |
| 	Offset int64
 | |
| 	Size   int64
 | |
| }
 | |
| 
 | |
| // A Reloc describes a relocation applied to a memory image to refer
 | |
| // to an address within a particular symbol.
 | |
| type Reloc struct {
 | |
| 	// The bytes at [Offset, Offset+Size) within the containing Sym
 | |
| 	// should be updated to refer to the address Add bytes after the start
 | |
| 	// of the symbol Sym.
 | |
| 	Offset int
 | |
| 	Size   int
 | |
| 	Sym    SymID
 | |
| 	Add    int
 | |
| 
 | |
| 	// The Type records the form of address expected in the bytes
 | |
| 	// described by the previous fields: absolute, PC-relative, and so on.
 | |
| 	// TODO(rsc): The interpretation of Type is not exposed by this package.
 | |
| 	Type obj.RelocType
 | |
| }
 | |
| 
 | |
| // A Var describes a variable in a function stack frame: a declared
 | |
| // local variable, an input argument, or an output result.
 | |
| type Var struct {
 | |
| 	// The combination of Name, Kind, and Offset uniquely
 | |
| 	// identifies a variable in a function stack frame.
 | |
| 	// Using fewer of these - in particular, using only Name - does not.
 | |
| 	Name   string // Name of variable.
 | |
| 	Kind   int    // TODO(rsc): Define meaning.
 | |
| 	Offset int    // Frame offset. TODO(rsc): Define meaning.
 | |
| 
 | |
| 	Type SymID // Go type for variable.
 | |
| }
 | |
| 
 | |
| // Func contains additional per-symbol information specific to functions.
 | |
| type Func struct {
 | |
| 	Args     int        // size in bytes of argument frame: inputs and outputs
 | |
| 	Frame    int        // size in bytes of local variable frame
 | |
| 	Leaf     bool       // function omits save of link register (ARM)
 | |
| 	NoSplit  bool       // function omits stack split prologue
 | |
| 	Var      []Var      // detail about local variables
 | |
| 	PCSP     Data       // PC → SP offset map
 | |
| 	PCFile   Data       // PC → file number map (index into File)
 | |
| 	PCLine   Data       // PC → line number map
 | |
| 	PCData   []Data     // PC → runtime support data map
 | |
| 	FuncData []FuncData // non-PC-specific runtime support data
 | |
| 	File     []string   // paths indexed by PCFile
 | |
| }
 | |
| 
 | |
| // TODO: Add PCData []byte and PCDataIter (similar to liblink).
 | |
| 
 | |
| // A FuncData is a single function-specific data value.
 | |
| type FuncData struct {
 | |
| 	Sym    SymID // symbol holding data
 | |
| 	Offset int64 // offset into symbol for funcdata pointer
 | |
| }
 | |
| 
 | |
| // A Package is a parsed Go object file or archive defining a Go package.
 | |
| type Package struct {
 | |
| 	ImportPath string   // import path denoting this package
 | |
| 	Imports    []string // packages imported by this package
 | |
| 	SymRefs    []SymID  // list of symbol names and versions referred to by this pack
 | |
| 	Syms       []*Sym   // symbols defined by this package
 | |
| 	MaxVersion int      // maximum Version in any SymID in Syms
 | |
| 	Arch       string   // architecture
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	archiveHeader = []byte("!<arch>\n")
 | |
| 	archiveMagic  = []byte("`\n")
 | |
| 	goobjHeader   = []byte("go objec") // truncated to size of archiveHeader
 | |
| 
 | |
| 	errCorruptArchive   = errors.New("corrupt archive")
 | |
| 	errTruncatedArchive = errors.New("truncated archive")
 | |
| 	errCorruptObject    = errors.New("corrupt object file")
 | |
| 	errNotObject        = errors.New("unrecognized object file format")
 | |
| )
 | |
| 
 | |
| // An objReader is an object file reader.
 | |
| type objReader struct {
 | |
| 	p          *Package
 | |
| 	b          *bufio.Reader
 | |
| 	f          io.ReadSeeker
 | |
| 	err        error
 | |
| 	offset     int64
 | |
| 	dataOffset int64
 | |
| 	limit      int64
 | |
| 	tmp        [256]byte
 | |
| 	pkgprefix  string
 | |
| }
 | |
| 
 | |
| // importPathToPrefix returns the prefix that will be used in the
 | |
| // final symbol table for the given import path.
 | |
| // We escape '%', '"', all control characters and non-ASCII bytes,
 | |
| // and any '.' after the final slash.
 | |
| //
 | |
| // See ../../../cmd/ld/lib.c:/^pathtoprefix and
 | |
| // ../../../cmd/gc/subr.c:/^pathtoprefix.
 | |
| func importPathToPrefix(s string) string {
 | |
| 	// find index of last slash, if any, or else -1.
 | |
| 	// used for determining whether an index is after the last slash.
 | |
| 	slash := strings.LastIndex(s, "/")
 | |
| 
 | |
| 	// check for chars that need escaping
 | |
| 	n := 0
 | |
| 	for r := 0; r < len(s); r++ {
 | |
| 		if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
 | |
| 			n++
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// quick exit
 | |
| 	if n == 0 {
 | |
| 		return s
 | |
| 	}
 | |
| 
 | |
| 	// escape
 | |
| 	const hex = "0123456789abcdef"
 | |
| 	p := make([]byte, 0, len(s)+2*n)
 | |
| 	for r := 0; r < len(s); r++ {
 | |
| 		if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
 | |
| 			p = append(p, '%', hex[c>>4], hex[c&0xF])
 | |
| 		} else {
 | |
| 			p = append(p, c)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return string(p)
 | |
| }
 | |
| 
 | |
| // init initializes r to read package p from f.
 | |
| func (r *objReader) init(f io.ReadSeeker, p *Package) {
 | |
| 	r.f = f
 | |
| 	r.p = p
 | |
| 	r.offset, _ = f.Seek(0, io.SeekCurrent)
 | |
| 	r.limit, _ = f.Seek(0, io.SeekEnd)
 | |
| 	f.Seek(r.offset, io.SeekStart)
 | |
| 	r.b = bufio.NewReader(f)
 | |
| 	r.pkgprefix = importPathToPrefix(p.ImportPath) + "."
 | |
| }
 | |
| 
 | |
| // error records that an error occurred.
 | |
| // It returns only the first error, so that an error
 | |
| // caused by an earlier error does not discard information
 | |
| // about the earlier error.
 | |
| func (r *objReader) error(err error) error {
 | |
| 	if r.err == nil {
 | |
| 		if err == io.EOF {
 | |
| 			err = io.ErrUnexpectedEOF
 | |
| 		}
 | |
| 		r.err = err
 | |
| 	}
 | |
| 	// panic("corrupt") // useful for debugging
 | |
| 	return r.err
 | |
| }
 | |
| 
 | |
| // readByte reads and returns a byte from the input file.
 | |
| // On I/O error or EOF, it records the error but returns byte 0.
 | |
| // A sequence of 0 bytes will eventually terminate any
 | |
| // parsing state in the object file. In particular, it ends the
 | |
| // reading of a varint.
 | |
| func (r *objReader) readByte() byte {
 | |
| 	if r.err != nil {
 | |
| 		return 0
 | |
| 	}
 | |
| 	if r.offset >= r.limit {
 | |
| 		r.error(io.ErrUnexpectedEOF)
 | |
| 		return 0
 | |
| 	}
 | |
| 	b, err := r.b.ReadByte()
 | |
| 	if err != nil {
 | |
| 		if err == io.EOF {
 | |
| 			err = io.ErrUnexpectedEOF
 | |
| 		}
 | |
| 		r.error(err)
 | |
| 		b = 0
 | |
| 	} else {
 | |
| 		r.offset++
 | |
| 	}
 | |
| 	return b
 | |
| }
 | |
| 
 | |
| // read reads exactly len(b) bytes from the input file.
 | |
| // If an error occurs, read returns the error but also
 | |
| // records it, so it is safe for callers to ignore the result
 | |
| // as long as delaying the report is not a problem.
 | |
| func (r *objReader) readFull(b []byte) error {
 | |
| 	if r.err != nil {
 | |
| 		return r.err
 | |
| 	}
 | |
| 	if r.offset+int64(len(b)) > r.limit {
 | |
| 		return r.error(io.ErrUnexpectedEOF)
 | |
| 	}
 | |
| 	n, err := io.ReadFull(r.b, b)
 | |
| 	r.offset += int64(n)
 | |
| 	if err != nil {
 | |
| 		return r.error(err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // readInt reads a zigzag varint from the input file.
 | |
| func (r *objReader) readInt() int {
 | |
| 	var u uint64
 | |
| 
 | |
| 	for shift := uint(0); ; shift += 7 {
 | |
| 		if shift >= 64 {
 | |
| 			r.error(errCorruptObject)
 | |
| 			return 0
 | |
| 		}
 | |
| 		c := r.readByte()
 | |
| 		u |= uint64(c&0x7F) << shift
 | |
| 		if c&0x80 == 0 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	v := int64(u>>1) ^ (int64(u) << 63 >> 63)
 | |
| 	if int64(int(v)) != v {
 | |
| 		r.error(errCorruptObject) // TODO
 | |
| 		return 0
 | |
| 	}
 | |
| 	return int(v)
 | |
| }
 | |
| 
 | |
| // readString reads a length-delimited string from the input file.
 | |
| func (r *objReader) readString() string {
 | |
| 	n := r.readInt()
 | |
| 	buf := make([]byte, n)
 | |
| 	r.readFull(buf)
 | |
| 	return string(buf)
 | |
| }
 | |
| 
 | |
| // readSymID reads a SymID from the input file.
 | |
| func (r *objReader) readSymID() SymID {
 | |
| 	i := r.readInt()
 | |
| 	return r.p.SymRefs[i]
 | |
| }
 | |
| 
 | |
| func (r *objReader) readRef() {
 | |
| 	name, vers := r.readString(), r.readInt()
 | |
| 
 | |
| 	// In a symbol name in an object file, "". denotes the
 | |
| 	// prefix for the package in which the object file has been found.
 | |
| 	// Expand it.
 | |
| 	name = strings.Replace(name, `"".`, r.pkgprefix, -1)
 | |
| 
 | |
| 	// An individual object file only records version 0 (extern) or 1 (static).
 | |
| 	// To make static symbols unique across all files being read, we
 | |
| 	// replace version 1 with the version corresponding to the current
 | |
| 	// file number. The number is incremented on each call to parseObject.
 | |
| 	if vers != 0 {
 | |
| 		vers = r.p.MaxVersion
 | |
| 	}
 | |
| 	r.p.SymRefs = append(r.p.SymRefs, SymID{name, vers})
 | |
| }
 | |
| 
 | |
| // readData reads a data reference from the input file.
 | |
| func (r *objReader) readData() Data {
 | |
| 	n := r.readInt()
 | |
| 	d := Data{Offset: r.dataOffset, Size: int64(n)}
 | |
| 	r.dataOffset += int64(n)
 | |
| 	return d
 | |
| }
 | |
| 
 | |
| // skip skips n bytes in the input.
 | |
| func (r *objReader) skip(n int64) {
 | |
| 	if n < 0 {
 | |
| 		r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip"))
 | |
| 	}
 | |
| 	if n < int64(len(r.tmp)) {
 | |
| 		// Since the data is so small, a just reading from the buffered
 | |
| 		// reader is better than flushing the buffer and seeking.
 | |
| 		r.readFull(r.tmp[:n])
 | |
| 	} else if n <= int64(r.b.Buffered()) {
 | |
| 		// Even though the data is not small, it has already been read.
 | |
| 		// Advance the buffer instead of seeking.
 | |
| 		for n > int64(len(r.tmp)) {
 | |
| 			r.readFull(r.tmp[:])
 | |
| 			n -= int64(len(r.tmp))
 | |
| 		}
 | |
| 		r.readFull(r.tmp[:n])
 | |
| 	} else {
 | |
| 		// Seek, giving up buffered data.
 | |
| 		_, err := r.f.Seek(r.offset+n, io.SeekStart)
 | |
| 		if err != nil {
 | |
| 			r.error(err)
 | |
| 		}
 | |
| 		r.offset += n
 | |
| 		r.b.Reset(r.f)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Parse parses an object file or archive from r,
 | |
| // assuming that its import path is pkgpath.
 | |
| func Parse(r io.ReadSeeker, pkgpath string) (*Package, error) {
 | |
| 	if pkgpath == "" {
 | |
| 		pkgpath = `""`
 | |
| 	}
 | |
| 	p := new(Package)
 | |
| 	p.ImportPath = pkgpath
 | |
| 
 | |
| 	var rd objReader
 | |
| 	rd.init(r, p)
 | |
| 	err := rd.readFull(rd.tmp[:8])
 | |
| 	if err != nil {
 | |
| 		if err == io.EOF {
 | |
| 			err = io.ErrUnexpectedEOF
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	switch {
 | |
| 	default:
 | |
| 		return nil, errNotObject
 | |
| 
 | |
| 	case bytes.Equal(rd.tmp[:8], archiveHeader):
 | |
| 		if err := rd.parseArchive(); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	case bytes.Equal(rd.tmp[:8], goobjHeader):
 | |
| 		if err := rd.parseObject(goobjHeader); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return p, nil
 | |
| }
 | |
| 
 | |
| // trimSpace removes trailing spaces from b and returns the corresponding string.
 | |
| // This effectively parses the form used in archive headers.
 | |
| func trimSpace(b []byte) string {
 | |
| 	return string(bytes.TrimRight(b, " "))
 | |
| }
 | |
| 
 | |
| // parseArchive parses a Unix archive of Go object files.
 | |
| // TODO(rsc): Need to skip non-Go object files.
 | |
| // TODO(rsc): Maybe record table of contents in r.p so that
 | |
| // linker can avoid having code to parse archives too.
 | |
| func (r *objReader) parseArchive() error {
 | |
| 	for r.offset < r.limit {
 | |
| 		if err := r.readFull(r.tmp[:60]); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		data := r.tmp[:60]
 | |
| 
 | |
| 		// Each file is preceded by this text header (slice indices in first column):
 | |
| 		//	 0:16	name
 | |
| 		//	16:28 date
 | |
| 		//	28:34 uid
 | |
| 		//	34:40 gid
 | |
| 		//	40:48 mode
 | |
| 		//	48:58 size
 | |
| 		//	58:60 magic - `\n
 | |
| 		// We only care about name, size, and magic.
 | |
| 		// The fields are space-padded on the right.
 | |
| 		// The size is in decimal.
 | |
| 		// The file data - size bytes - follows the header.
 | |
| 		// Headers are 2-byte aligned, so if size is odd, an extra padding
 | |
| 		// byte sits between the file data and the next header.
 | |
| 		// The file data that follows is padded to an even number of bytes:
 | |
| 		// if size is odd, an extra padding byte is inserted betw the next header.
 | |
| 		if len(data) < 60 {
 | |
| 			return errTruncatedArchive
 | |
| 		}
 | |
| 		if !bytes.Equal(data[58:60], archiveMagic) {
 | |
| 			return errCorruptArchive
 | |
| 		}
 | |
| 		name := trimSpace(data[0:16])
 | |
| 		size, err := strconv.ParseInt(trimSpace(data[48:58]), 10, 64)
 | |
| 		if err != nil {
 | |
| 			return errCorruptArchive
 | |
| 		}
 | |
| 		data = data[60:]
 | |
| 		fsize := size + size&1
 | |
| 		if fsize < 0 || fsize < size {
 | |
| 			return errCorruptArchive
 | |
| 		}
 | |
| 		switch name {
 | |
| 		case "__.PKGDEF":
 | |
| 			r.skip(size)
 | |
| 		default:
 | |
| 			oldLimit := r.limit
 | |
| 			r.limit = r.offset + size
 | |
| 			if err := r.parseObject(nil); err != nil {
 | |
| 				return fmt.Errorf("parsing archive member %q: %v", name, err)
 | |
| 			}
 | |
| 			r.skip(r.limit - r.offset)
 | |
| 			r.limit = oldLimit
 | |
| 		}
 | |
| 		if size&1 != 0 {
 | |
| 			r.skip(1)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // parseObject parses a single Go object file.
 | |
| // The prefix is the bytes already read from the file,
 | |
| // typically in order to detect that this is an object file.
 | |
| // The object file consists of a textual header ending in "\n!\n"
 | |
| // and then the part we want to parse begins.
 | |
| // The format of that part is defined in a comment at the top
 | |
| // of src/liblink/objfile.c.
 | |
| func (r *objReader) parseObject(prefix []byte) error {
 | |
| 	r.p.MaxVersion++
 | |
| 	h := make([]byte, 0, 256)
 | |
| 	h = append(h, prefix...)
 | |
| 	var c1, c2, c3 byte
 | |
| 	for {
 | |
| 		c1, c2, c3 = c2, c3, r.readByte()
 | |
| 		h = append(h, c3)
 | |
| 		// The new export format can contain 0 bytes.
 | |
| 		// Don't consider them errors, only look for r.err != nil.
 | |
| 		if r.err != nil {
 | |
| 			return errCorruptObject
 | |
| 		}
 | |
| 		if c1 == '\n' && c2 == '!' && c3 == '\n' {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	hs := strings.Fields(string(h))
 | |
| 	if len(hs) >= 4 {
 | |
| 		r.p.Arch = hs[3]
 | |
| 	}
 | |
| 	// TODO: extract OS + build ID if/when we need it
 | |
| 
 | |
| 	r.readFull(r.tmp[:8])
 | |
| 	if !bytes.Equal(r.tmp[:8], []byte("\x00\x00go17ld")) {
 | |
| 		return r.error(errCorruptObject)
 | |
| 	}
 | |
| 
 | |
| 	b := r.readByte()
 | |
| 	if b != 1 {
 | |
| 		return r.error(errCorruptObject)
 | |
| 	}
 | |
| 
 | |
| 	// Direct package dependencies.
 | |
| 	for {
 | |
| 		s := r.readString()
 | |
| 		if s == "" {
 | |
| 			break
 | |
| 		}
 | |
| 		r.p.Imports = append(r.p.Imports, s)
 | |
| 	}
 | |
| 
 | |
| 	r.p.SymRefs = []SymID{{"", 0}}
 | |
| 	for {
 | |
| 		if b := r.readByte(); b != 0xfe {
 | |
| 			if b != 0xff {
 | |
| 				return r.error(errCorruptObject)
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		r.readRef()
 | |
| 	}
 | |
| 
 | |
| 	dataLength := r.readInt()
 | |
| 	r.readInt() // n relocations - ignore
 | |
| 	r.readInt() // n pcdata - ignore
 | |
| 	r.readInt() // n autom - ignore
 | |
| 	r.readInt() // n funcdata - ignore
 | |
| 	r.readInt() // n files - ignore
 | |
| 
 | |
| 	r.dataOffset = r.offset
 | |
| 	r.skip(int64(dataLength))
 | |
| 
 | |
| 	// Symbols.
 | |
| 	for {
 | |
| 		if b := r.readByte(); b != 0xfe {
 | |
| 			if b != 0xff {
 | |
| 				return r.error(errCorruptObject)
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		typ := r.readInt()
 | |
| 		s := &Sym{SymID: r.readSymID()}
 | |
| 		r.p.Syms = append(r.p.Syms, s)
 | |
| 		s.Kind = SymKind(typ)
 | |
| 		flags := r.readInt()
 | |
| 		s.DupOK = flags&1 != 0
 | |
| 		s.Size = r.readInt()
 | |
| 		s.Type = r.readSymID()
 | |
| 		s.Data = r.readData()
 | |
| 		s.Reloc = make([]Reloc, r.readInt())
 | |
| 		for i := range s.Reloc {
 | |
| 			rel := &s.Reloc[i]
 | |
| 			rel.Offset = r.readInt()
 | |
| 			rel.Size = r.readInt()
 | |
| 			rel.Type = obj.RelocType(r.readInt())
 | |
| 			rel.Add = r.readInt()
 | |
| 			rel.Sym = r.readSymID()
 | |
| 		}
 | |
| 
 | |
| 		if s.Kind == STEXT {
 | |
| 			f := new(Func)
 | |
| 			s.Func = f
 | |
| 			f.Args = r.readInt()
 | |
| 			f.Frame = r.readInt()
 | |
| 			flags := r.readInt()
 | |
| 			f.Leaf = flags&1 != 0
 | |
| 			f.NoSplit = r.readInt() != 0
 | |
| 			f.Var = make([]Var, r.readInt())
 | |
| 			for i := range f.Var {
 | |
| 				v := &f.Var[i]
 | |
| 				v.Name = r.readSymID().Name
 | |
| 				v.Offset = r.readInt()
 | |
| 				v.Kind = r.readInt()
 | |
| 				v.Type = r.readSymID()
 | |
| 			}
 | |
| 
 | |
| 			f.PCSP = r.readData()
 | |
| 			f.PCFile = r.readData()
 | |
| 			f.PCLine = r.readData()
 | |
| 			f.PCData = make([]Data, r.readInt())
 | |
| 			for i := range f.PCData {
 | |
| 				f.PCData[i] = r.readData()
 | |
| 			}
 | |
| 			f.FuncData = make([]FuncData, r.readInt())
 | |
| 			for i := range f.FuncData {
 | |
| 				f.FuncData[i].Sym = r.readSymID()
 | |
| 			}
 | |
| 			for i := range f.FuncData {
 | |
| 				f.FuncData[i].Offset = int64(r.readInt()) // TODO
 | |
| 			}
 | |
| 			f.File = make([]string, r.readInt())
 | |
| 			for i := range f.File {
 | |
| 				f.File[i] = r.readSymID().Name
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r.readFull(r.tmp[:7])
 | |
| 	if !bytes.Equal(r.tmp[:7], []byte("\xffgo17ld")) {
 | |
| 		return r.error(errCorruptObject)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (r *Reloc) String(insnOffset uint64) string {
 | |
| 	delta := r.Offset - int(insnOffset)
 | |
| 	s := fmt.Sprintf("[%d:%d]%s", delta, delta+r.Size, r.Type)
 | |
| 	if r.Sym.Name != "" {
 | |
| 		if r.Add != 0 {
 | |
| 			return fmt.Sprintf("%s:%s+%d", s, r.Sym.Name, r.Add)
 | |
| 		}
 | |
| 		return fmt.Sprintf("%s:%s", s, r.Sym.Name)
 | |
| 	}
 | |
| 	if r.Add != 0 {
 | |
| 		return fmt.Sprintf("%s:%d", s, r.Add)
 | |
| 	}
 | |
| 	return s
 | |
| }
 | 
