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