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
|
||
|
}
|