// 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. // This file is a modified version of cmd/internal/goobj/readnew.go package goobj2 import ( "bufio" "bytes" "errors" "fmt" "io" "io/ioutil" "net/url" "os" "os/exec" "path/filepath" "strconv" "strings" "github.com/Binject/debug/goobj2/internal/goobj2" "github.com/Binject/debug/goobj2/internal/objabi" ) const ( CompilerObjName = "__.PKGDEF" archiveHeaderLen = 60 ) // A Package is a parsed Go object file or archive defining a Go package. type Package struct { ArchiveMembers []ArchiveMember ImportPath string os string arch string } func (p Package) OS() string { return p.os } func (p Package) Arch() string { return p.arch } type ArchiveMember struct { ArchiveHeader ArchiveHeader ObjHeader goobj2.Header Imports []goobj2.ImportedPkg Packages []string DWARFFileList []string SymDefs []*Sym NonPkgSymDefs []*Sym NonPkgSymRefs []*Sym SymRefs []SymRef IsDataObj bool textSyms []*Sym symMap map[int]*Sym } func (a ArchiveMember) IsCompilerObj() bool { return a.ArchiveHeader.Name == CompilerObjName } type ArchiveHeader struct { Name string Date string UID string GID string Mode string Size int64 Data []byte } // A Sym is a named symbol in an object file. type Sym struct { Name string ABI uint16 Kind SymKind // kind of symbol Flag uint8 Size uint32 // size of corresponding data Align uint32 Type *SymRef // symbol for Go type information Data []byte // memory image of symbol Reloc []Reloc // relocations to apply to Data Func *Func // additional data for functions } type SymRef struct { Name string goobj2.SymRef } // A Reloc describes a relocation applied to a memory image to refer // to an address within a particular symbol. type Reloc struct { Name string // 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 int64 Size int64 Sym goobj2.SymRef Add int64 // 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 objabi.RelocType } // Func contains additional per-symbol information specific to functions. type Func struct { Args int64 // size in bytes of argument frame: inputs and outputs Frame int64 // size in bytes of local variable frame PCSP []byte // PC → SP offset map PCFile []byte // PC → file number map (index into File) PCLine []byte // PC → line number map PCInline []byte // PC → inline tree index map PCData [][]byte // PC → runtime support data map FuncData []FuncData // non-PC-specific runtime support data File []SymRef // paths indexed by PCFile InlTree []*InlinedCall FuncInfo *SymRef DwarfInfo *SymRef DwarfLoc *SymRef DwarfRanges *SymRef DwarfDebugLines *SymRef dataSymIdx int } // A FuncData is a single function-specific data value. type FuncData struct { Sym *SymRef // symbol holding data Offset uint32 // offset into symbol for funcdata pointer } // An InlinedCall is a node in an InlTree. // See cmd/internal/obj.InlTree for details. type InlinedCall struct { Parent int32 File SymRef Line int32 Func SymRef ParentPC int32 } // A SymKind describes the kind of memory represented by a symbol. type SymKind uint8 // Defined SymKind values. // Copied from cmd/internal/objabi const ( // An otherwise invalid zero value for the type Sxxx SymKind = iota // Executable instructions STEXT // Read only static data SRODATA // Static data that does not contain any pointers SNOPTRDATA // Static data SDATA // Statically data that is initially all 0s SBSS // Statically data that is initially all 0s and does not contain pointers SNOPTRBSS // Thread-local data that is initially all 0s STLSBSS // Debugging data SDWARFINFO SDWARFRANGE SDWARFLOC SDWARFLINES // ABI alias. An ABI alias symbol is an empty symbol with a // single relocation with 0 size that references the native // function implementation symbol. // // TODO(austin): Remove this and all uses once the compiler // generates real ABI wrappers rather than symbol aliases. SABIALIAS // Coverage instrumentation counter for libfuzzer. SLIBFUZZER_EXTRA_COUNTER ) type ImportCfg struct { ImportMap map[string]string Packages map[string]ExportInfo } type ExportInfo struct { Path string IsSharedLib bool } func ParseImportCfg(path string) (importCfg ImportCfg, err error) { data, err := ioutil.ReadFile(path) if err != nil { return importCfg, fmt.Errorf("error reading importcfg: %v", err) } lines := bytes.Count(data, []byte("\n")) if lines == -1 { return importCfg, errors.New("error parsing importcfg: could not find any newlines") } importCfg.ImportMap = make(map[string]string) importCfg.Packages = make(map[string]ExportInfo, lines) for lineNum, line := range strings.Split(string(data), "\n") { lineNum++ // 1-based line = strings.TrimSpace(line) if line == "" { continue } if line == "" || strings.HasPrefix(line, "#") { continue } var verb, args string if i := strings.Index(line, " "); i < 0 { verb = line } else { verb, args = line[:i], strings.TrimSpace(line[i+1:]) } var before, after string if i := strings.Index(args, "="); i >= 0 { before, after = args[:i], args[i+1:] } switch verb { default: return importCfg, fmt.Errorf("error parsing importcfg: %s:%d: unknown directive %q", path, lineNum, verb) case "importmap": if before == "" || after == "" { return importCfg, fmt.Errorf(`error parsing importcfg: %s:%d: invalid importmap: syntax is "importmap path=path"`, path, lineNum) } importCfg.ImportMap[before] = after case "packagefile": if before == "" || after == "" { return importCfg, fmt.Errorf(`error parsing importcfg: %s:%d: invalid packagefile: syntax is "packagefile path=filename"`, path, lineNum) } importCfg.Packages[before] = ExportInfo{after, false} case "packageshlib": if before == "" || after == "" { return importCfg, fmt.Errorf(`error parsing importcfg: %s:%d: invalid packageshlib: syntax is "packageshlib path=filename"`, path, lineNum) } importCfg.Packages[before] = ExportInfo{after, true} } } return importCfg, nil } var ( archiveHeader = []byte("!\n") archiveMagic = []byte("`\n") goobjHeader = []byte("go objec") // truncated to size of archiveHeader archivePathPrefix = filepath.Join("$GOROOT", "pkg") 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 *os.File err error offset int64 limit int64 tmp [256]byte pkgprefix string objStart int64 } // init initializes r to read package p from f. func (r *objReader) init(f *os.File, 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) if p != nil { r.pkgprefix = objabi.PathToPrefix(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 } // peek returns the next n bytes without advancing the reader. func (r *objReader) peek(n int) ([]byte, error) { if r.err != nil { return nil, r.err } if r.offset >= r.limit { r.error(io.ErrUnexpectedEOF) return nil, r.err } b, err := r.b.Peek(n) if err != nil { if err != bufio.ErrBufferFull { r.error(err) } } return b, 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() int64 { 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 } } return int64(u>>1) ^ (int64(u) << 63 >> 63) } // 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) } } // ImportMap is a function that returns the path of a Go object // from a given import path. If the import path is not known, // an empty string should be returned. type ImportMap = func(importPath string) (objectPath string) // Parse parses an object file or archive from objPath, assuming that // its import path is pkgpath. A function that returns paths of object // files from import paths can optionally be passed in as importMap // to optimize looking up paths to dependencies' object files. func Parse(objPath, pkgPath string, importMap ImportMap) (*Package, error) { p := new(Package) p.ImportPath = pkgPath if _, err := parse(objPath, p, importMap, false); err != nil { return nil, err } return p, nil } func parse(objPath string, p *Package, importMap ImportMap, returnReader bool) (rr *goobj2.Reader, err error) { f, openErr := os.Open(objPath) if err != nil { return nil, openErr } defer func() { closeErr := f.Close() if closeErr != nil && err == nil { err = closeErr } }() var rd objReader rd.init(f, 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): rr, err = rd.parseArchive(importMap, returnReader) if err != nil { return nil, err } case bytes.Equal(rd.tmp[:8], goobjHeader): var am *ArchiveMember rr, am, _, err = rd.parseObject(goobjHeader, importMap, returnReader) if err != nil { return nil, err } p.ArchiveMembers = append(p.ArchiveMembers, *am) } return rr, 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. func (r *objReader) parseArchive(importMap ImportMap, returnReader bool) (*goobj2.Reader, error) { for r.offset < r.limit { if err := r.readFull(r.tmp[:archiveHeaderLen]); err != nil { return nil, err } data := r.tmp[:archiveHeaderLen] // 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 // 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 between the next header. if len(data) < archiveHeaderLen { return nil, errTruncatedArchive } if !bytes.Equal(data[58:60], archiveMagic) { return nil, errCorruptArchive } var ar ArchiveHeader ar.Name = trimSpace(data[0:16]) ar.Date = trimSpace(data[16:28]) ar.UID = trimSpace(data[28:34]) ar.GID = trimSpace(data[34:40]) ar.Mode = trimSpace(data[40:48]) size, err := strconv.ParseInt(trimSpace(data[48:58]), 10, 64) if err != nil { return nil, errCorruptArchive } data = data[archiveHeaderLen:] fsize := size + size&1 if fsize < 0 || fsize < size { return nil, errCorruptArchive } ar.Size = size var am *ArchiveMember switch ar.Name { case CompilerObjName: ar.Data = make([]byte, size) if err := r.readFull(ar.Data); err != nil { return nil, err } if fsize != size { ar.Data = append(ar.Data, 0x00) } am = new(ArchiveMember) am.ArchiveHeader = ar am.IsDataObj = true default: oldLimit := r.limit r.limit = r.offset + size p, err := r.peek(8) if err != nil { return nil, err } if bytes.Equal(p, goobjHeader) { var rr *goobj2.Reader rr, am, data, err = r.parseObject(nil, importMap, returnReader) if err != nil { return nil, fmt.Errorf("parsing archive member %q: %v", ar.Name, err) } if returnReader { return rr, nil } ar.Data = data am.ArchiveHeader = ar } else { ar.Data = make([]byte, size) if err := r.readFull(ar.Data); err != nil { return nil, err } if fsize != size { ar.Data = append(ar.Data, 0x00) } am = &ArchiveMember{ArchiveHeader: ar, IsDataObj: true} } r.skip(r.limit - r.offset) r.limit = oldLimit } if size&1 != 0 { r.skip(1) } if r.p != nil && am != nil { r.p.ArchiveMembers = append(r.p.ArchiveMembers, *am) } } return nil, 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, importMap ImportMap, returnReader bool) (*goobj2.Reader, *ArchiveMember, []byte, error) { 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 nil, nil, nil, errCorruptObject } if c1 == '\n' && c2 == '!' && c3 == '\n' { break } } hs := strings.Fields(string(h)) if len(hs) >= 4 && r.p != nil { r.p.os = hs[2] r.p.arch = hs[3] } p, err := r.peek(8) if err != nil { return nil, nil, nil, err } if !bytes.Equal(p, []byte(goobj2.Magic)) { return nil, nil, nil, errNotObject } r.objStart = r.offset length := r.limit - r.offset objbytes := make([]byte, length) r.readFull(objbytes) rr := goobj2.NewReaderFromBytes(objbytes, false) if rr == nil { return nil, nil, nil, errCorruptObject } if returnReader { return rr, nil, nil, nil } var am ArchiveMember am.symMap = make(map[int]*Sym) // Header am.ObjHeader = rr.Header() // Imports for _, p := range rr.Autolib() { am.Imports = append(am.Imports, p) } // Referenced packages am.Packages = rr.Pkglist() am.Packages = am.Packages[1:] // skip first package which is always an empty string // Dwarf file table am.DWARFFileList = make([]string, rr.NDwarfFile()) for i := 0; i < len(am.DWARFFileList); i++ { am.DWARFFileList[i] = rr.DwarfFile(i) } // Name of referenced indexed symbols. nrefName := rr.NRefName() refNames := make(map[goobj2.SymRef]string, nrefName) am.SymRefs = make([]SymRef, 0, nrefName) for i := 0; i < nrefName; i++ { rn := rr.RefName(i) sym, name := rn.Sym(), rn.Name(rr) refNames[sym] = name am.SymRefs = append(am.SymRefs, SymRef{name, sym}) } resolveSymRefName := func(s goobj2.SymRef) string { var i int switch p := s.PkgIdx; p { case goobj2.PkgIdxInvalid: if s.SymIdx != 0 { panic("bad sym ref") } return "" case goobj2.PkgIdxNone: i = int(s.SymIdx) + rr.NSym() case goobj2.PkgIdxBuiltin: name, _ := goobj2.BuiltinName(int(s.SymIdx)) return name case goobj2.PkgIdxSelf: i = int(s.SymIdx) default: return refNames[s] } sym := rr.Sym(i) return sym.Name(rr) } // Symbols pcdataBase := rr.PcdataBase() ndef := rr.NSym() + rr.NNonpkgdef() var inlFuncsToResolve []*InlinedCall parseSym := func(i, j int, symDefs []*Sym) { osym := rr.Sym(i) sym := &Sym{ Name: osym.Name(rr), ABI: osym.ABI(), Kind: SymKind(osym.Type()), Flag: osym.Flag(), Size: osym.Siz(), Align: osym.Align(), } symDefs[j] = sym am.symMap[i] = sym if i >= ndef { return // not a defined symbol from here } if sym.Kind == STEXT { am.textSyms = append(am.textSyms, sym) } // Symbol data sym.Data = rr.Data(i) // Reloc relocs := rr.Relocs(i) sym.Reloc = make([]Reloc, len(relocs)) for j := range relocs { rel := &relocs[j] s := rel.Sym() sym.Reloc[j] = Reloc{ Name: resolveSymRefName(s), Offset: int64(rel.Off()), Size: int64(rel.Siz()), Type: objabi.RelocType(rel.Type()), Add: rel.Add(), Sym: s, } } // Aux symbol info isym := -1 funcdata := make([]*SymRef, 0, 4) var funcInfo, dinfo, dloc, dranges, dlines *SymRef auxs := rr.Auxs(i) for j := range auxs { a := &auxs[j] switch a.Type() { case goobj2.AuxGotype: s := a.Sym() sym.Type = &SymRef{resolveSymRefName(s), s} case goobj2.AuxFuncInfo: sr := a.Sym() if sr.PkgIdx != goobj2.PkgIdxSelf { panic("funcinfo symbol not defined in current package") } funcInfo = &SymRef{resolveSymRefName(sr), sr} isym = int(a.Sym().SymIdx) case goobj2.AuxFuncdata: sr := a.Sym() funcdata = append(funcdata, &SymRef{resolveSymRefName(sr), sr}) case goobj2.AuxDwarfInfo: sr := a.Sym() dinfo = &SymRef{resolveSymRefName(sr), sr} case goobj2.AuxDwarfLoc: sr := a.Sym() dloc = &SymRef{resolveSymRefName(sr), sr} case goobj2.AuxDwarfRanges: sr := a.Sym() dranges = &SymRef{resolveSymRefName(sr), sr} case goobj2.AuxDwarfLines: sr := a.Sym() dlines = &SymRef{resolveSymRefName(sr), sr} default: panic("unknown aux type") } } // Symbol Info if isym == -1 { return } b := rr.Data(isym) info := goobj2.FuncInfo{} info.Read(b) info.Pcdata = append(info.Pcdata, info.PcdataEnd) // for the ease of knowing where it ends f := &Func{ Args: int64(info.Args), Frame: int64(info.Locals), PCSP: rr.BytesAt(pcdataBase+info.Pcsp, int(info.Pcfile-info.Pcsp)), PCFile: rr.BytesAt(pcdataBase+info.Pcfile, int(info.Pcline-info.Pcfile)), PCLine: rr.BytesAt(pcdataBase+info.Pcline, int(info.Pcinline-info.Pcline)), PCInline: rr.BytesAt(pcdataBase+info.Pcinline, int(info.Pcdata[0]-info.Pcinline)), PCData: make([][]byte, len(info.Pcdata)-1), // -1 as we appended one above FuncData: make([]FuncData, len(info.Funcdataoff)), File: make([]SymRef, len(info.File)), InlTree: make([]*InlinedCall, len(info.InlTree)), FuncInfo: funcInfo, dataSymIdx: isym, } sym.Func = f for k := range f.PCData { f.PCData[k] = rr.BytesAt(pcdataBase+info.Pcdata[k], int(info.Pcdata[k+1]-info.Pcdata[k])) } for k := range f.FuncData { f.FuncData[k] = FuncData{funcdata[k], info.Funcdataoff[k]} } for k := range f.File { f.File[k] = SymRef{resolveSymRefName(info.File[k]), info.File[k]} } for k := range f.InlTree { inl := &info.InlTree[k] f.InlTree[k] = &InlinedCall{ Parent: inl.Parent, File: SymRef{resolveSymRefName(inl.File), inl.File}, Line: inl.Line, Func: SymRef{resolveSymRefName(inl.Func), inl.Func}, ParentPC: inl.ParentPC, } if f.InlTree[k].Func.Name == "" { inlFuncsToResolve = append(inlFuncsToResolve, f.InlTree[k]) } } if dinfo != nil { f.DwarfInfo = dinfo } if dloc != nil { f.DwarfLoc = dloc } if dranges != nil { f.DwarfRanges = dranges } if dlines != nil { f.DwarfDebugLines = dlines } } // Symbol definitions nsymDefs := rr.NSym() am.SymDefs = make([]*Sym, nsymDefs) for i := 0; i < nsymDefs; i++ { parseSym(i, i, am.SymDefs) } // Non-pkg symbol definitions nNonPkgDefs := rr.NNonpkgdef() am.NonPkgSymDefs = make([]*Sym, nNonPkgDefs) parsedSyms := nsymDefs for i := 0; i < nNonPkgDefs; i++ { parseSym(i+parsedSyms, i, am.NonPkgSymDefs) } // Non-pkg symbol references nNonPkgRefs := rr.NNonpkgref() am.NonPkgSymRefs = make([]*Sym, nNonPkgRefs) parsedSyms += nNonPkgDefs for i := 0; i < nNonPkgRefs; i++ { parseSym(i+parsedSyms, i, am.NonPkgSymRefs) } // Symbol references were already parsed above // Resolve missing inlined function names if len(inlFuncsToResolve) == 0 { return nil, &am, h, nil } objReaders := make([]*goobj2.Reader, len(am.Packages)) for _, inl := range inlFuncsToResolve { if pkgIdx := inl.Func.PkgIdx; objReaders[pkgIdx-1] == nil { pkgName := am.Packages[pkgIdx-1] archivePath, err := getArchivePath(pkgName, importMap) if err != nil { return nil, nil, nil, fmt.Errorf("error resolving path of objfile %s: %v", pkgName, err) } rr, err := parse(archivePath, nil, nil, true) if err != nil { return nil, nil, nil, fmt.Errorf("error parsing objfile %s: %v", pkgName, err) } objReaders[pkgIdx-1] = rr } rr := objReaders[inl.Func.PkgIdx-1] inl.Func.Name = rr.Sym(int(inl.Func.SymIdx)).Name(rr) } return nil, &am, h, nil } func getArchivePath(pkg string, importMap ImportMap) (s string, err error) { // try to get the archive path from the importMap first if importMap != nil { if path := importMap(pkg); path != "" { return path, nil } } // for whatever reason, the Go compiler will url-encode // packages paths that have certain symbols in them, // like a period. ex gopkg.in/yaml.v2 => gopkg/in/yaml%2ev2 if strings.ContainsRune(pkg, '%') { pkg, err = url.QueryUnescape(pkg) if err != nil { return "", err } } cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkg) path, err := cmd.Output() if err != nil { return "", err } return strings.TrimSpace(string(path)), nil }