Source file src/debug/plan9obj/file.go

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6  Package plan9obj implements access to Plan 9 a.out object files.
     7  
     8  # Security
     9  
    10  This package is not designed to be hardened against adversarial inputs, and is
    11  outside the scope of https://go.dev/security/policy. In particular, only basic
    12  validation is done when parsing object files. As such, care should be taken when
    13  parsing untrusted inputs, as parsing malformed files may consume significant
    14  resources, or cause panics.
    15  */
    16  package plan9obj
    17  
    18  import (
    19  	"encoding/binary"
    20  	"errors"
    21  	"fmt"
    22  	"internal/saferio"
    23  	"io"
    24  	"os"
    25  )
    26  
    27  // A FileHeader represents a Plan 9 a.out file header.
    28  type FileHeader struct {
    29  	Magic       uint32
    30  	Bss         uint32
    31  	Entry       uint64
    32  	PtrSize     int
    33  	LoadAddress uint64
    34  	HdrSize     uint64
    35  }
    36  
    37  // A File represents an open Plan 9 a.out file.
    38  type File struct {
    39  	FileHeader
    40  	Sections []*Section
    41  	closer   io.Closer
    42  }
    43  
    44  // A SectionHeader represents a single Plan 9 a.out section header.
    45  // This structure doesn't exist on-disk, but eases navigation
    46  // through the object file.
    47  type SectionHeader struct {
    48  	Name   string
    49  	Size   uint32
    50  	Offset uint32
    51  }
    52  
    53  // A Section represents a single section in a Plan 9 a.out file.
    54  type Section struct {
    55  	SectionHeader
    56  
    57  	// Embed ReaderAt for ReadAt method.
    58  	// Do not embed SectionReader directly
    59  	// to avoid having Read and Seek.
    60  	// If a client wants Read and Seek it must use
    61  	// Open() to avoid fighting over the seek offset
    62  	// with other clients.
    63  	io.ReaderAt
    64  	sr *io.SectionReader
    65  }
    66  
    67  // Data reads and returns the contents of the Plan 9 a.out section.
    68  func (s *Section) Data() ([]byte, error) {
    69  	return saferio.ReadDataAt(s.sr, uint64(s.Size), 0)
    70  }
    71  
    72  // Open returns a new ReadSeeker reading the Plan 9 a.out section.
    73  func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
    74  
    75  // A Symbol represents an entry in a Plan 9 a.out symbol table section.
    76  type Sym struct {
    77  	Value uint64
    78  	Type  rune
    79  	Name  string
    80  }
    81  
    82  /*
    83   * Plan 9 a.out reader
    84   */
    85  
    86  // formatError is returned by some operations if the data does
    87  // not have the correct format for an object file.
    88  type formatError struct {
    89  	off int
    90  	msg string
    91  	val any
    92  }
    93  
    94  func (e *formatError) Error() string {
    95  	msg := e.msg
    96  	if e.val != nil {
    97  		msg += fmt.Sprintf(" '%v'", e.val)
    98  	}
    99  	msg += fmt.Sprintf(" in record at byte %#x", e.off)
   100  	return msg
   101  }
   102  
   103  // Open opens the named file using [os.Open] and prepares it for use as a Plan 9 a.out binary.
   104  func Open(name string) (*File, error) {
   105  	f, err := os.Open(name)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	ff, err := NewFile(f)
   110  	if err != nil {
   111  		f.Close()
   112  		return nil, err
   113  	}
   114  	ff.closer = f
   115  	return ff, nil
   116  }
   117  
   118  // Close closes the [File].
   119  // If the [File] was created using [NewFile] directly instead of [Open],
   120  // Close has no effect.
   121  func (f *File) Close() error {
   122  	var err error
   123  	if f.closer != nil {
   124  		err = f.closer.Close()
   125  		f.closer = nil
   126  	}
   127  	return err
   128  }
   129  
   130  func parseMagic(magic []byte) (uint32, error) {
   131  	m := binary.BigEndian.Uint32(magic)
   132  	switch m {
   133  	case Magic386, MagicAMD64, MagicARM:
   134  		return m, nil
   135  	}
   136  	return 0, &formatError{0, "bad magic number", magic}
   137  }
   138  
   139  // NewFile creates a new [File] for accessing a Plan 9 binary in an underlying reader.
   140  // The Plan 9 binary is expected to start at position 0 in the ReaderAt.
   141  func NewFile(r io.ReaderAt) (*File, error) {
   142  	sr := io.NewSectionReader(r, 0, 1<<63-1)
   143  	// Read and decode Plan 9 magic
   144  	var magic [4]byte
   145  	if _, err := r.ReadAt(magic[:], 0); err != nil {
   146  		return nil, err
   147  	}
   148  	_, err := parseMagic(magic[:])
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	ph := new(prog)
   154  	if err := binary.Read(sr, binary.BigEndian, ph); err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	f := &File{FileHeader: FileHeader{
   159  		Magic:       ph.Magic,
   160  		Bss:         ph.Bss,
   161  		Entry:       uint64(ph.Entry),
   162  		PtrSize:     4,
   163  		LoadAddress: 0x1000,
   164  		HdrSize:     4 * 8,
   165  	}}
   166  
   167  	if ph.Magic&Magic64 != 0 {
   168  		if err := binary.Read(sr, binary.BigEndian, &f.Entry); err != nil {
   169  			return nil, err
   170  		}
   171  		f.PtrSize = 8
   172  		f.LoadAddress = 0x200000
   173  		f.HdrSize += 8
   174  	}
   175  
   176  	var sects = []struct {
   177  		name string
   178  		size uint32
   179  	}{
   180  		{"text", ph.Text},
   181  		{"data", ph.Data},
   182  		{"syms", ph.Syms},
   183  		{"spsz", ph.Spsz},
   184  		{"pcsz", ph.Pcsz},
   185  	}
   186  
   187  	f.Sections = make([]*Section, 5)
   188  
   189  	off := uint32(f.HdrSize)
   190  
   191  	for i, sect := range sects {
   192  		s := new(Section)
   193  		s.SectionHeader = SectionHeader{
   194  			Name:   sect.name,
   195  			Size:   sect.size,
   196  			Offset: off,
   197  		}
   198  		off += sect.size
   199  		s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Size))
   200  		s.ReaderAt = s.sr
   201  		f.Sections[i] = s
   202  	}
   203  
   204  	return f, nil
   205  }
   206  
   207  func walksymtab(data []byte, ptrsz int, fn func(sym) error) error {
   208  	var order binary.ByteOrder = binary.BigEndian
   209  	var s sym
   210  	p := data
   211  	for len(p) >= 4 {
   212  		// Symbol type, value.
   213  		if len(p) < ptrsz {
   214  			return &formatError{len(data), "unexpected EOF", nil}
   215  		}
   216  		// fixed-width value
   217  		if ptrsz == 8 {
   218  			s.value = order.Uint64(p[0:8])
   219  			p = p[8:]
   220  		} else {
   221  			s.value = uint64(order.Uint32(p[0:4]))
   222  			p = p[4:]
   223  		}
   224  
   225  		if len(p) < 1 {
   226  			return &formatError{len(data), "unexpected EOF", nil}
   227  		}
   228  		typ := p[0] & 0x7F
   229  		s.typ = typ
   230  		p = p[1:]
   231  
   232  		// Name.
   233  		var i int
   234  		var nnul int
   235  		for i = 0; i < len(p); i++ {
   236  			if p[i] == 0 {
   237  				nnul = 1
   238  				break
   239  			}
   240  		}
   241  		switch typ {
   242  		case 'z', 'Z':
   243  			p = p[i+nnul:]
   244  			for i = 0; i+2 <= len(p); i += 2 {
   245  				if p[i] == 0 && p[i+1] == 0 {
   246  					nnul = 2
   247  					break
   248  				}
   249  			}
   250  		}
   251  		if len(p) < i+nnul {
   252  			return &formatError{len(data), "unexpected EOF", nil}
   253  		}
   254  		s.name = p[0:i]
   255  		i += nnul
   256  		p = p[i:]
   257  
   258  		fn(s)
   259  	}
   260  	return nil
   261  }
   262  
   263  // newTable decodes the Go symbol table in data,
   264  // returning an in-memory representation.
   265  func newTable(symtab []byte, ptrsz int) ([]Sym, error) {
   266  	var n int
   267  	err := walksymtab(symtab, ptrsz, func(s sym) error {
   268  		n++
   269  		return nil
   270  	})
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	fname := make(map[uint16]string)
   276  	syms := make([]Sym, 0, n)
   277  	err = walksymtab(symtab, ptrsz, func(s sym) error {
   278  		n := len(syms)
   279  		syms = syms[0 : n+1]
   280  		ts := &syms[n]
   281  		ts.Type = rune(s.typ)
   282  		ts.Value = s.value
   283  		switch s.typ {
   284  		default:
   285  			ts.Name = string(s.name)
   286  		case 'z', 'Z':
   287  			for i := 0; i < len(s.name); i += 2 {
   288  				eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
   289  				elt, ok := fname[eltIdx]
   290  				if !ok {
   291  					return &formatError{-1, "bad filename code", eltIdx}
   292  				}
   293  				if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
   294  					ts.Name += "/"
   295  				}
   296  				ts.Name += elt
   297  			}
   298  		}
   299  		switch s.typ {
   300  		case 'f':
   301  			fname[uint16(s.value)] = ts.Name
   302  		}
   303  		return nil
   304  	})
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	return syms, nil
   310  }
   311  
   312  // ErrNoSymbols is returned by [File.Symbols] if there is no such section
   313  // in the File.
   314  var ErrNoSymbols = errors.New("no symbol section")
   315  
   316  // Symbols returns the symbol table for f.
   317  func (f *File) Symbols() ([]Sym, error) {
   318  	symtabSection := f.Section("syms")
   319  	if symtabSection == nil {
   320  		return nil, ErrNoSymbols
   321  	}
   322  
   323  	symtab, err := symtabSection.Data()
   324  	if err != nil {
   325  		return nil, errors.New("cannot load symbol section")
   326  	}
   327  
   328  	return newTable(symtab, f.PtrSize)
   329  }
   330  
   331  // Section returns a section with the given name, or nil if no such
   332  // section exists.
   333  func (f *File) Section(name string) *Section {
   334  	for _, s := range f.Sections {
   335  		if s.Name == name {
   336  			return s
   337  		}
   338  	}
   339  	return nil
   340  }
   341  

View as plain text