Source file src/debug/pe/symbol.go

     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 := make([]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 := make([]*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  

View as plain text