symbol.mx raw

   1  // Copyright 2016 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  package pe
   6  
   7  import (
   8  	"encoding/binary"
   9  	"errors"
  10  	"fmt"
  11  	"internal/saferio"
  12  	"io"
  13  	"unsafe"
  14  )
  15  
  16  const COFFSymbolSize = 18
  17  
  18  // COFFSymbol represents single COFF symbol table record.
  19  type COFFSymbol struct {
  20  	Name               [8]uint8
  21  	Value              uint32
  22  	SectionNumber      int16
  23  	Type               uint16
  24  	StorageClass       uint8
  25  	NumberOfAuxSymbols uint8
  26  }
  27  
  28  // readCOFFSymbols reads in the symbol table for a PE file, returning
  29  // a slice of COFFSymbol objects. The PE format includes both primary
  30  // symbols (whose fields are described by COFFSymbol above) and
  31  // auxiliary symbols; all symbols are 18 bytes in size. The auxiliary
  32  // symbols for a given primary symbol are placed following it in the
  33  // array, e.g.
  34  //
  35  //	...
  36  //	k+0:  regular sym k
  37  //	k+1:    1st aux symbol for k
  38  //	k+2:    2nd aux symbol for k
  39  //	k+3:  regular sym k+3
  40  //	k+4:    1st aux symbol for k+3
  41  //	k+5:  regular sym k+5
  42  //	k+6:  regular sym k+6
  43  //
  44  // The PE format allows for several possible aux symbol formats. For
  45  // more info see:
  46  //
  47  //	https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-symbol-records
  48  //
  49  // At the moment this package only provides APIs for looking at
  50  // aux symbols of format 5 (associated with section definition symbols).
  51  func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) {
  52  	if fh.PointerToSymbolTable == 0 {
  53  		return nil, nil
  54  	}
  55  	if fh.NumberOfSymbols <= 0 {
  56  		return nil, nil
  57  	}
  58  	_, err := r.Seek(int64(fh.PointerToSymbolTable), io.SeekStart)
  59  	if err != nil {
  60  		return nil, fmt.Errorf("fail to seek to symbol table: %v", err)
  61  	}
  62  	c := saferio.SliceCap[COFFSymbol](uint64(fh.NumberOfSymbols))
  63  	if c < 0 {
  64  		return nil, errors.New("too many symbols; file may be corrupt")
  65  	}
  66  	syms := []COFFSymbol{:0:c}
  67  	naux := 0
  68  	for k := uint32(0); k < fh.NumberOfSymbols; k++ {
  69  		var sym COFFSymbol
  70  		if naux == 0 {
  71  			// Read a primary symbol.
  72  			err = binary.Read(r, binary.LittleEndian, &sym)
  73  			if err != nil {
  74  				return nil, fmt.Errorf("fail to read symbol table: %v", err)
  75  			}
  76  			// Record how many auxiliary symbols it has.
  77  			naux = int(sym.NumberOfAuxSymbols)
  78  		} else {
  79  			// Read an aux symbol. At the moment we assume all
  80  			// aux symbols are format 5 (obviously this doesn't always
  81  			// hold; more cases will be needed below if more aux formats
  82  			// are supported in the future).
  83  			naux--
  84  			aux := (*COFFSymbolAuxFormat5)(unsafe.Pointer(&sym))
  85  			err = binary.Read(r, binary.LittleEndian, aux)
  86  			if err != nil {
  87  				return nil, fmt.Errorf("fail to read symbol table: %v", err)
  88  			}
  89  		}
  90  		syms = append(syms, sym)
  91  	}
  92  	if naux != 0 {
  93  		return nil, fmt.Errorf("fail to read symbol table: %d aux symbols unread", naux)
  94  	}
  95  	return syms, nil
  96  }
  97  
  98  // isSymNameOffset checks symbol name if it is encoded as offset into string table.
  99  func isSymNameOffset(name [8]byte) (bool, uint32) {
 100  	if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 {
 101  		offset := binary.LittleEndian.Uint32(name[4:])
 102  		if offset == 0 {
 103  			// symbol has no name
 104  			return false, 0
 105  		}
 106  		return true, offset
 107  	}
 108  	return false, 0
 109  }
 110  
 111  // FullName finds real name of symbol sym. Normally name is stored
 112  // in sym.Name, but if it is longer then 8 characters, it is stored
 113  // in COFF string table st instead.
 114  func (sym *COFFSymbol) FullName(st StringTable) (string, error) {
 115  	if ok, offset := isSymNameOffset(sym.Name); ok {
 116  		return st.String(offset)
 117  	}
 118  	return cstring(sym.Name[:]), nil
 119  }
 120  
 121  func removeAuxSymbols(allsyms []COFFSymbol, st StringTable) ([]*Symbol, error) {
 122  	if len(allsyms) == 0 {
 123  		return nil, nil
 124  	}
 125  	syms := []*Symbol{:0}
 126  	aux := uint8(0)
 127  	for _, sym := range allsyms {
 128  		if aux > 0 {
 129  			aux--
 130  			continue
 131  		}
 132  		name, err := sym.FullName(st)
 133  		if err != nil {
 134  			return nil, err
 135  		}
 136  		aux = sym.NumberOfAuxSymbols
 137  		s := &Symbol{
 138  			Name:          name,
 139  			Value:         sym.Value,
 140  			SectionNumber: sym.SectionNumber,
 141  			Type:          sym.Type,
 142  			StorageClass:  sym.StorageClass,
 143  		}
 144  		syms = append(syms, s)
 145  	}
 146  	return syms, nil
 147  }
 148  
 149  // Symbol is similar to [COFFSymbol] with Name field replaced
 150  // by Go string. Symbol also does not have NumberOfAuxSymbols.
 151  type Symbol struct {
 152  	Name          string
 153  	Value         uint32
 154  	SectionNumber int16
 155  	Type          uint16
 156  	StorageClass  uint8
 157  }
 158  
 159  // COFFSymbolAuxFormat5 describes the expected form of an aux symbol
 160  // attached to a section definition symbol. The PE format defines a
 161  // number of different aux symbol formats: format 1 for function
 162  // definitions, format 2 for .be and .ef symbols, and so on. Format 5
 163  // holds extra info associated with a section definition, including
 164  // number of relocations + line numbers, as well as COMDAT info. See
 165  // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-format-5-section-definitions
 166  // for more on what's going on here.
 167  type COFFSymbolAuxFormat5 struct {
 168  	Size           uint32
 169  	NumRelocs      uint16
 170  	NumLineNumbers uint16
 171  	Checksum       uint32
 172  	SecNum         uint16
 173  	Selection      uint8
 174  	_              [3]uint8 // padding
 175  }
 176  
 177  // These constants make up the possible values for the 'Selection'
 178  // field in an AuxFormat5.
 179  const (
 180  	IMAGE_COMDAT_SELECT_NODUPLICATES = 1
 181  	IMAGE_COMDAT_SELECT_ANY          = 2
 182  	IMAGE_COMDAT_SELECT_SAME_SIZE    = 3
 183  	IMAGE_COMDAT_SELECT_EXACT_MATCH  = 4
 184  	IMAGE_COMDAT_SELECT_ASSOCIATIVE  = 5
 185  	IMAGE_COMDAT_SELECT_LARGEST      = 6
 186  )
 187  
 188  // COFFSymbolReadSectionDefAux returns a blob of auxiliary information
 189  // (including COMDAT info) for a section definition symbol. Here 'idx'
 190  // is the index of a section symbol in the main [COFFSymbol] array for
 191  // the File. Return value is a pointer to the appropriate aux symbol
 192  // struct. For more info, see:
 193  //
 194  // auxiliary symbols: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-symbol-records
 195  // COMDAT sections: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#comdat-sections-object-only
 196  // auxiliary info for section definitions: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-format-5-section-definitions
 197  func (f *File) COFFSymbolReadSectionDefAux(idx int) (*COFFSymbolAuxFormat5, error) {
 198  	var rv *COFFSymbolAuxFormat5
 199  	if idx < 0 || idx >= len(f.COFFSymbols) {
 200  		return rv, fmt.Errorf("invalid symbol index")
 201  	}
 202  	pesym := &f.COFFSymbols[idx]
 203  	const IMAGE_SYM_CLASS_STATIC = 3
 204  	if pesym.StorageClass != uint8(IMAGE_SYM_CLASS_STATIC) {
 205  		return rv, fmt.Errorf("incorrect symbol storage class")
 206  	}
 207  	if pesym.NumberOfAuxSymbols == 0 || idx+1 >= len(f.COFFSymbols) {
 208  		return rv, fmt.Errorf("aux symbol unavailable")
 209  	}
 210  	// Locate and return a pointer to the successor aux symbol.
 211  	pesymn := &f.COFFSymbols[idx+1]
 212  	rv = (*COFFSymbolAuxFormat5)(unsafe.Pointer(pesymn))
 213  	return rv, nil
 214  }
 215