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
5package pe
6
7import (
8	"encoding/binary"
9	"errors"
10	"fmt"
11	"internal/saferio"
12	"io"
13	"unsafe"
14)
15
16const COFFSymbolSize = 18
17
18// COFFSymbol represents single COFF symbol table record.
19type 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).
51func 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.
99func isSymNameOffset(name [8]byte) (bool, uint32) {
100	if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 {
101		return true, binary.LittleEndian.Uint32(name[4:])
102	}
103	return false, 0
104}
105
106// FullName finds real name of symbol sym. Normally name is stored
107// in sym.Name, but if it is longer then 8 characters, it is stored
108// in COFF string table st instead.
109func (sym *COFFSymbol) FullName(st StringTable) (string, error) {
110	if ok, offset := isSymNameOffset(sym.Name); ok {
111		return st.String(offset)
112	}
113	return cstring(sym.Name[:]), nil
114}
115
116func removeAuxSymbols(allsyms []COFFSymbol, st StringTable) ([]*Symbol, error) {
117	if len(allsyms) == 0 {
118		return nil, nil
119	}
120	syms := make([]*Symbol, 0)
121	aux := uint8(0)
122	for _, sym := range allsyms {
123		if aux > 0 {
124			aux--
125			continue
126		}
127		name, err := sym.FullName(st)
128		if err != nil {
129			return nil, err
130		}
131		aux = sym.NumberOfAuxSymbols
132		s := &Symbol{
133			Name:          name,
134			Value:         sym.Value,
135			SectionNumber: sym.SectionNumber,
136			Type:          sym.Type,
137			StorageClass:  sym.StorageClass,
138		}
139		syms = append(syms, s)
140	}
141	return syms, nil
142}
143
144// Symbol is similar to [COFFSymbol] with Name field replaced
145// by Go string. Symbol also does not have NumberOfAuxSymbols.
146type Symbol struct {
147	Name          string
148	Value         uint32
149	SectionNumber int16
150	Type          uint16
151	StorageClass  uint8
152}
153
154// COFFSymbolAuxFormat5 describes the expected form of an aux symbol
155// attached to a section definition symbol. The PE format defines a
156// number of different aux symbol formats: format 1 for function
157// definitions, format 2 for .be and .ef symbols, and so on. Format 5
158// holds extra info associated with a section definition, including
159// number of relocations + line numbers, as well as COMDAT info. See
160// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-format-5-section-definitions
161// for more on what's going on here.
162type COFFSymbolAuxFormat5 struct {
163	Size           uint32
164	NumRelocs      uint16
165	NumLineNumbers uint16
166	Checksum       uint32
167	SecNum         uint16
168	Selection      uint8
169	_              [3]uint8 // padding
170}
171
172// These constants make up the possible values for the 'Selection'
173// field in an AuxFormat5.
174const (
175	IMAGE_COMDAT_SELECT_NODUPLICATES = 1
176	IMAGE_COMDAT_SELECT_ANY          = 2
177	IMAGE_COMDAT_SELECT_SAME_SIZE    = 3
178	IMAGE_COMDAT_SELECT_EXACT_MATCH  = 4
179	IMAGE_COMDAT_SELECT_ASSOCIATIVE  = 5
180	IMAGE_COMDAT_SELECT_LARGEST      = 6
181)
182
183// COFFSymbolReadSectionDefAux returns a blob of auxiliary information
184// (including COMDAT info) for a section definition symbol. Here 'idx'
185// is the index of a section symbol in the main [COFFSymbol] array for
186// the File. Return value is a pointer to the appropriate aux symbol
187// struct. For more info, see:
188//
189// auxiliary symbols: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-symbol-records
190// COMDAT sections: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#comdat-sections-object-only
191// auxiliary info for section definitions: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-format-5-section-definitions
192func (f *File) COFFSymbolReadSectionDefAux(idx int) (*COFFSymbolAuxFormat5, error) {
193	var rv *COFFSymbolAuxFormat5
194	if idx < 0 || idx >= len(f.COFFSymbols) {
195		return rv, fmt.Errorf("invalid symbol index")
196	}
197	pesym := &f.COFFSymbols[idx]
198	const IMAGE_SYM_CLASS_STATIC = 3
199	if pesym.StorageClass != uint8(IMAGE_SYM_CLASS_STATIC) {
200		return rv, fmt.Errorf("incorrect symbol storage class")
201	}
202	if pesym.NumberOfAuxSymbols == 0 || idx+1 >= len(f.COFFSymbols) {
203		return rv, fmt.Errorf("aux symbol unavailable")
204	}
205	// Locate and return a pointer to the successor aux symbol.
206	pesymn := &f.COFFSymbols[idx+1]
207	rv = (*COFFSymbolAuxFormat5)(unsafe.Pointer(pesymn))
208	return rv, nil
209}
210