blob: 9a067e2b9dada513bd2a6f029b7298d9ce6410c1 [file] [log] [blame]
// DO NOT EDIT. Generated by code.google.com/p/rsc/cmd/bundle
// bundle -p main -x goobj_ debug/goobj
/* read.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 main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strconv"
"strings"
)
// A SymKind describes the kind of memory represented by a symbol.
type goobj_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 (
_ goobj_SymKind = iota
// readonly, executable
goobj_STEXT
goobj_SELFRXSECT
// readonly, non-executable
goobj_STYPE
goobj_SSTRING
goobj_SGOSTRING
goobj_SGOFUNC
goobj_SRODATA
goobj_SFUNCTAB
goobj_STYPELINK
goobj_SSYMTAB // TODO: move to unmapped section
goobj_SPCLNTAB
goobj_SELFROSECT
// writable, non-executable
goobj_SMACHOPLT
goobj_SELFSECT
goobj_SMACHO // Mach-O __nl_symbol_ptr
goobj_SMACHOGOT
goobj_SNOPTRDATA
goobj_SINITARR
goobj_SDATA
goobj_SWINDOWS
goobj_SBSS
goobj_SNOPTRBSS
goobj_STLSBSS
// not mapped
goobj_SXREF
goobj_SMACHOSYMSTR
goobj_SMACHOSYMTAB
goobj_SMACHOINDIRECTPLT
goobj_SMACHOINDIRECTGOT
goobj_SFILE
goobj_SFILEPATH
goobj_SCONST
goobj_SDYNIMPORT
goobj_SHOSTOBJ
)
var goobj_symKindStrings = []string{
goobj_SBSS: "SBSS",
goobj_SCONST: "SCONST",
goobj_SDATA: "SDATA",
goobj_SDYNIMPORT: "SDYNIMPORT",
goobj_SELFROSECT: "SELFROSECT",
goobj_SELFRXSECT: "SELFRXSECT",
goobj_SELFSECT: "SELFSECT",
goobj_SFILE: "SFILE",
goobj_SFILEPATH: "SFILEPATH",
goobj_SFUNCTAB: "SFUNCTAB",
goobj_SGOFUNC: "SGOFUNC",
goobj_SGOSTRING: "SGOSTRING",
goobj_SHOSTOBJ: "SHOSTOBJ",
goobj_SINITARR: "SINITARR",
goobj_SMACHO: "SMACHO",
goobj_SMACHOGOT: "SMACHOGOT",
goobj_SMACHOINDIRECTGOT: "SMACHOINDIRECTGOT",
goobj_SMACHOINDIRECTPLT: "SMACHOINDIRECTPLT",
goobj_SMACHOPLT: "SMACHOPLT",
goobj_SMACHOSYMSTR: "SMACHOSYMSTR",
goobj_SMACHOSYMTAB: "SMACHOSYMTAB",
goobj_SNOPTRBSS: "SNOPTRBSS",
goobj_SNOPTRDATA: "SNOPTRDATA",
goobj_SPCLNTAB: "SPCLNTAB",
goobj_SRODATA: "SRODATA",
goobj_SSTRING: "SSTRING",
goobj_SSYMTAB: "SSYMTAB",
goobj_STEXT: "STEXT",
goobj_STLSBSS: "STLSBSS",
goobj_STYPE: "STYPE",
goobj_STYPELINK: "STYPELINK",
goobj_SWINDOWS: "SWINDOWS",
goobj_SXREF: "SXREF",
}
func (k goobj_SymKind) String() string {
if k < 0 || int(k) >= len(goobj_symKindStrings) {
return fmt.Sprintf("SymKind(%d)", k)
}
return goobj_symKindStrings[k]
}
// A Sym is a named symbol in an object file.
type goobj_Sym struct {
goobj_SymID // symbol identifier (name and version)
Kind goobj_SymKind // kind of symbol
DupOK bool // are duplicate definitions okay?
Size int // size of corresponding data
Type goobj_SymID // symbol for Go type information
Data goobj_Data // memory image of symbol
Reloc []goobj_Reloc // relocations to apply to Data
Func *goobj_Func // additional data for functions
}
// A SymID - the combination of Name and Version - uniquely identifies
// a symbol within a package.
type goobj_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 goobj_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 goobj_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 goobj_Reloc struct {
// The bytes at [Offset, Offset+Size) within the memory image
// should be updated to refer to the address Add bytes after the start
// of the symbol Sym.
Offset int
Size int
Sym goobj_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 int
}
// A Var describes a variable in a function stack frame: a declared
// local variable, an input argument, or an output result.
type goobj_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 goobj_SymID // Go type for variable.
}
// Func contains additional per-symbol information specific to functions.
type goobj_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 []goobj_Var // detail about local variables
PCSP goobj_Data // PC → SP offset map
PCFile goobj_Data // PC → file number map (index into File)
PCLine goobj_Data // PC → line number map
PCData []goobj_Data // PC → runtime support data map
FuncData []goobj_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 goobj_FuncData struct {
Sym goobj_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 goobj_Package struct {
ImportPath string // import path denoting this package
Imports []string // packages imported by this package
Syms []*goobj_Sym // symbols defined by this package
MaxVersion int // maximum Version in any SymID in Syms
}
var (
goobj_archiveHeader = []byte("!<arch>\n")
goobj_archiveMagic = []byte("`\n")
goobj_goobjHeader = []byte("go objec") // truncated to size of archiveHeader
goobj_errCorruptArchive = errors.New("corrupt archive")
goobj_errTruncatedArchive = errors.New("truncated archive")
goobj_errNotArchive = errors.New("unrecognized archive format")
goobj_errCorruptObject = errors.New("corrupt object file")
goobj_errTruncatedObject = errors.New("truncated object file")
goobj_errNotObject = errors.New("unrecognized object file format")
)
// An objReader is an object file reader.
type goobj_objReader struct {
p *goobj_Package
b *bufio.Reader
f io.ReadSeeker
err error
offset int64
limit int64
tmp [256]byte
pkg string
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 goobj_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 *goobj_objReader) init(f io.ReadSeeker, p *goobj_Package) {
r.f = f
r.p = p
r.offset, _ = f.Seek(0, 1)
r.limit, _ = f.Seek(0, 2)
f.Seek(r.offset, 0)
r.b = bufio.NewReader(f)
r.pkgprefix = goobj_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 *goobj_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 *goobj_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 *goobj_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 *goobj_objReader) readInt() int {
var u uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
r.error(goobj_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(goobj_errCorruptObject) // TODO
return 0
}
return int(v)
}
// readString reads a length-delimited string from the input file.
func (r *goobj_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 *goobj_objReader) readSymID() goobj_SymID {
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
}
return goobj_SymID{name, vers}
}
// readData reads a data reference from the input file.
func (r *goobj_objReader) readData() goobj_Data {
n := r.readInt()
d := goobj_Data{Offset: r.offset, Size: int64(n)}
r.skip(int64(n))
return d
}
// skip skips n bytes in the input.
func (r *goobj_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, 0)
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 goobj_Parse(r io.ReadSeeker, pkgpath string) (*goobj_Package, error) {
if pkgpath == "" {
pkgpath = `""`
}
p := new(goobj_Package)
p.ImportPath = pkgpath
var rd goobj_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, goobj_errNotObject
case bytes.Equal(rd.tmp[:8], goobj_archiveHeader):
if err := rd.parseArchive(); err != nil {
return nil, err
}
case bytes.Equal(rd.tmp[:8], goobj_goobjHeader):
if err := rd.parseObject(goobj_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 goobj_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 *goobj_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 goobj_errTruncatedArchive
}
if !bytes.Equal(data[58:60], goobj_archiveMagic) {
return goobj_errCorruptArchive
}
name := goobj_trimSpace(data[0:16])
size, err := strconv.ParseInt(goobj_trimSpace(data[48:58]), 10, 64)
if err != nil {
return goobj_errCorruptArchive
}
data = data[60:]
fsize := size + size&1
if fsize < 0 || fsize < size {
return goobj_errCorruptArchive
}
switch name {
case "__.SYMDEF", "__.GOSYMDEF", "__.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 *goobj_objReader) parseObject(prefix []byte) error {
// TODO(rsc): Maybe use prefix and the initial input to
// record the header line from the file, which would
// give the architecture and other version information.
r.p.MaxVersion++
var c1, c2, c3 byte
for {
c1, c2, c3 = c2, c3, r.readByte()
if c3 == 0 { // NUL or EOF, either is bad
return goobj_errCorruptObject
}
if c1 == '\n' && c2 == '!' && c3 == '\n' {
break
}
}
r.readFull(r.tmp[:8])
if !bytes.Equal(r.tmp[:8], []byte("\x00\x00go13ld")) {
return r.error(goobj_errCorruptObject)
}
b := r.readByte()
if b != 1 {
return r.error(goobj_errCorruptObject)
}
// Direct package dependencies.
for {
s := r.readString()
if s == "" {
break
}
r.p.Imports = append(r.p.Imports, s)
}
// Symbols.
for {
if b := r.readByte(); b != 0xfe {
if b != 0xff {
return r.error(goobj_errCorruptObject)
}
break
}
typ := r.readInt()
s := &goobj_Sym{goobj_SymID: r.readSymID()}
r.p.Syms = append(r.p.Syms, s)
s.Kind = goobj_SymKind(typ)
s.DupOK = r.readInt() != 0
s.Size = r.readInt()
s.Type = r.readSymID()
s.Data = r.readData()
s.Reloc = make([]goobj_Reloc, r.readInt())
for i := range s.Reloc {
rel := &s.Reloc[i]
rel.Offset = r.readInt()
rel.Size = r.readInt()
rel.Type = r.readInt()
rel.Add = r.readInt()
r.readInt() // Xadd - ignored
rel.Sym = r.readSymID()
r.readSymID() // Xsym - ignored
}
if s.Kind == goobj_STEXT {
f := new(goobj_Func)
s.Func = f
f.Args = r.readInt()
f.Frame = r.readInt()
f.Leaf = r.readInt() != 0
f.NoSplit = r.readInt() != 0
f.Var = make([]goobj_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([]goobj_Data, r.readInt())
for i := range f.PCData {
f.PCData[i] = r.readData()
}
f.FuncData = make([]goobj_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("\xffgo13ld")) {
return r.error(goobj_errCorruptObject)
}
return nil
}