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