// Copyright 2014 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 plan9obj implements access to Plan 9 a.out object files. # Security This package is not designed to be hardened against adversarial inputs, and is outside the scope of https://go.dev/security/policy. In particular, only basic validation is done when parsing object files. As such, care should be taken when parsing untrusted inputs, as parsing malformed files may consume significant resources, or cause panics. */ package plan9obj import ( "encoding/binary" "errors" "fmt" "internal/saferio" "io" "os" ) // A FileHeader represents a Plan 9 a.out file header. type FileHeader struct { Magic uint32 Bss uint32 Entry uint64 PtrSize int LoadAddress uint64 HdrSize uint64 } // A File represents an open Plan 9 a.out file. type File struct { FileHeader Sections []*Section closer io.Closer } // A SectionHeader represents a single Plan 9 a.out section header. // This structure doesn't exist on-disk, but eases navigation // through the object file. type SectionHeader struct { Name string Size uint32 Offset uint32 } // A Section represents a single section in a Plan 9 a.out file. type Section struct { SectionHeader // Embed ReaderAt for ReadAt method. // Do not embed SectionReader directly // to avoid having Read and Seek. // If a client wants Read and Seek it must use // Open() to avoid fighting over the seek offset // with other clients. io.ReaderAt sr *io.SectionReader } // Data reads and returns the contents of the Plan 9 a.out section. func (s *Section) Data() ([]byte, error) { return saferio.ReadDataAt(s.sr, uint64(s.Size), 0) } // Open returns a new ReadSeeker reading the Plan 9 a.out section. func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) } // A Symbol represents an entry in a Plan 9 a.out symbol table section. type Sym struct { Value uint64 Type rune Name string } /* * Plan 9 a.out reader */ // formatError is returned by some operations if the data does // not have the correct format for an object file. type formatError struct { off int msg string val any } func (e *formatError) Error() string { msg := e.msg if e.val != nil { msg += fmt.Sprintf(" '%v'", e.val) } msg += fmt.Sprintf(" in record at byte %#x", e.off) return msg } // Open opens the named file using [os.Open] and prepares it for use as a Plan 9 a.out binary. func Open(name string) (*File, error) { f, err := os.Open(name) if err != nil { return nil, err } ff, err := NewFile(f) if err != nil { f.Close() return nil, err } ff.closer = f return ff, nil } // Close closes the [File]. // If the [File] was created using [NewFile] directly instead of [Open], // Close has no effect. func (f *File) Close() error { var err error if f.closer != nil { err = f.closer.Close() f.closer = nil } return err } func parseMagic(magic []byte) (uint32, error) { m := binary.BigEndian.Uint32(magic) switch m { case Magic386, MagicAMD64, MagicARM: return m, nil } return 0, &formatError{0, "bad magic number", magic} } // NewFile creates a new [File] for accessing a Plan 9 binary in an underlying reader. // The Plan 9 binary is expected to start at position 0 in the ReaderAt. func NewFile(r io.ReaderAt) (*File, error) { sr := io.NewSectionReader(r, 0, 1<<63-1) // Read and decode Plan 9 magic var magic [4]byte if _, err := r.ReadAt(magic[:], 0); err != nil { return nil, err } _, err := parseMagic(magic[:]) if err != nil { return nil, err } ph := new(prog) if err := binary.Read(sr, binary.BigEndian, ph); err != nil { return nil, err } f := &File{FileHeader: FileHeader{ Magic: ph.Magic, Bss: ph.Bss, Entry: uint64(ph.Entry), PtrSize: 4, LoadAddress: 0x1000, HdrSize: 4 * 8, }} if ph.Magic&Magic64 != 0 { if err := binary.Read(sr, binary.BigEndian, &f.Entry); err != nil { return nil, err } f.PtrSize = 8 f.LoadAddress = 0x200000 f.HdrSize += 8 } var sects = []struct { name string size uint32 }{ {"text", ph.Text}, {"data", ph.Data}, {"syms", ph.Syms}, {"spsz", ph.Spsz}, {"pcsz", ph.Pcsz}, } f.Sections = make([]*Section, 5) off := uint32(f.HdrSize) for i, sect := range sects { s := new(Section) s.SectionHeader = SectionHeader{ Name: sect.name, Size: sect.size, Offset: off, } off += sect.size s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Size)) s.ReaderAt = s.sr f.Sections[i] = s } return f, nil } func walksymtab(data []byte, ptrsz int, fn func(sym) error) error { var order binary.ByteOrder = binary.BigEndian var s sym p := data for len(p) >= 4 { // Symbol type, value. if len(p) < ptrsz { return &formatError{len(data), "unexpected EOF", nil} } // fixed-width value if ptrsz == 8 { s.value = order.Uint64(p[0:8]) p = p[8:] } else { s.value = uint64(order.Uint32(p[0:4])) p = p[4:] } if len(p) < 1 { return &formatError{len(data), "unexpected EOF", nil} } typ := p[0] & 0x7F s.typ = typ p = p[1:] // Name. var i int var nnul int for i = 0; i < len(p); i++ { if p[i] == 0 { nnul = 1 break } } switch typ { case 'z', 'Z': p = p[i+nnul:] for i = 0; i+2 <= len(p); i += 2 { if p[i] == 0 && p[i+1] == 0 { nnul = 2 break } } } if len(p) < i+nnul { return &formatError{len(data), "unexpected EOF", nil} } s.name = p[0:i] i += nnul p = p[i:] fn(s) } return nil } // newTable decodes the Go symbol table in data, // returning an in-memory representation. func newTable(symtab []byte, ptrsz int) ([]Sym, error) { var n int err := walksymtab(symtab, ptrsz, func(s sym) error { n++ return nil }) if err != nil { return nil, err } fname := make(map[uint16]string) syms := make([]Sym, 0, n) err = walksymtab(symtab, ptrsz, func(s sym) error { n := len(syms) syms = syms[0 : n+1] ts := &syms[n] ts.Type = rune(s.typ) ts.Value = s.value switch s.typ { default: ts.Name = string(s.name) case 'z', 'Z': for i := 0; i < len(s.name); i += 2 { eltIdx := binary.BigEndian.Uint16(s.name[i : i+2]) elt, ok := fname[eltIdx] if !ok { return &formatError{-1, "bad filename code", eltIdx} } if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' { ts.Name += "/" } ts.Name += elt } } switch s.typ { case 'f': fname[uint16(s.value)] = ts.Name } return nil }) if err != nil { return nil, err } return syms, nil } // ErrNoSymbols is returned by [File.Symbols] if there is no such section // in the File. var ErrNoSymbols = errors.New("no symbol section") // Symbols returns the symbol table for f. func (f *File) Symbols() ([]Sym, error) { symtabSection := f.Section("syms") if symtabSection == nil { return nil, ErrNoSymbols } symtab, err := symtabSection.Data() if err != nil { return nil, errors.New("cannot load symbol section") } return newTable(symtab, f.PtrSize) } // Section returns a section with the given name, or nil if no such // section exists. func (f *File) Section(name string) *Section { for _, s := range f.Sections { if s.Name == name { return s } } return nil }