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