Skip to content

Commit a441c98

Browse files
committed
debug/pe: add support for bigobj COFF format
This adds support for parsing bigobj COFF files, which use a different header format with a 32-bit section count, and symbols with a 32 bit section number. This fixes linking bigobj COFF with cgo. Fixes #24341
1 parent e8a4f50 commit a441c98

File tree

4 files changed

+273
-47
lines changed

4 files changed

+273
-47
lines changed

src/debug/pe/file.go

Lines changed: 160 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ import (
2929

3030
// A File represents an open PE file.
3131
type File struct {
32-
FileHeader
32+
// FileHeader is populated for regular COFF files
33+
FileHeader *FileHeader
34+
// BigObjHeader is populated for bigobj COFF files
35+
BigObjHeader *BigObjHeader
36+
3337
OptionalHeader any // of type *OptionalHeader32 or *OptionalHeader64
3438
Sections []*Section
3539
Symbols []*Symbol // COFF symbols with auxiliary symbol records removed
@@ -39,6 +43,69 @@ type File struct {
3943
closer io.Closer
4044
}
4145

46+
// IsBigObj reports whether the file is a bigobj COFF file.
47+
func (f *File) IsBigObj() bool {
48+
return f.BigObjHeader != nil
49+
}
50+
51+
// GetMachine returns the machine type from the appropriate header.
52+
func (f *File) GetMachine() uint16 {
53+
if f.BigObjHeader != nil {
54+
return f.BigObjHeader.Machine
55+
}
56+
return f.FileHeader.Machine
57+
}
58+
59+
// GetNumberOfSections returns the number of sections from the appropriate header.
60+
func (f *File) GetNumberOfSections() uint32 {
61+
if f.BigObjHeader != nil {
62+
return f.BigObjHeader.NumberOfSections
63+
}
64+
return uint32(f.FileHeader.NumberOfSections)
65+
}
66+
67+
// GetTimeDateStamp returns the timestamp from the appropriate header.
68+
func (f *File) GetTimeDateStamp() uint32 {
69+
if f.BigObjHeader != nil {
70+
return f.BigObjHeader.TimeDateStamp
71+
}
72+
return f.FileHeader.TimeDateStamp
73+
}
74+
75+
// GetPointerToSymbolTable returns the symbol table pointer from the appropriate header.
76+
func (f *File) GetPointerToSymbolTable() uint32 {
77+
if f.BigObjHeader != nil {
78+
return f.BigObjHeader.PointerToSymbolTable
79+
}
80+
return f.FileHeader.PointerToSymbolTable
81+
}
82+
83+
// GetNumberOfSymbols returns the number of symbols from the appropriate header.
84+
func (f *File) GetNumberOfSymbols() uint32 {
85+
if f.BigObjHeader != nil {
86+
return f.BigObjHeader.NumberOfSymbols
87+
}
88+
return f.FileHeader.NumberOfSymbols
89+
}
90+
91+
// GetSizeOfOptionalHeader returns the optional header size from the appropriate header.
92+
// BigObj files don't have optional headers, so this returns 0 for them.
93+
func (f *File) GetSizeOfOptionalHeader() uint16 {
94+
if f.BigObjHeader != nil {
95+
return 0
96+
}
97+
return f.FileHeader.SizeOfOptionalHeader
98+
}
99+
100+
// GetCharacteristics returns the characteristics from the appropriate header.
101+
// BigObj files don't have characteristics, so this returns 0 for them.
102+
func (f *File) GetCharacteristics() uint16 {
103+
if f.BigObjHeader != nil {
104+
return 0
105+
}
106+
return f.FileHeader.Characteristics
107+
}
108+
42109
// Open opens the named file using [os.Open] and prepares it for use as a PE binary.
43110
func Open(name string) (*File, error) {
44111
f, err := os.Open(name)
@@ -68,6 +135,69 @@ func (f *File) Close() error {
68135

69136
// TODO(brainman): add Load function, as a replacement for NewFile, that does not call removeAuxSymbols (for performance)
70137

138+
// isBigObjFormat detects if the reader contains a bigobj COFF file by checking
139+
// the signature and GUID. The reader position should be at the start of the COFF header.
140+
func isBigObjFormat(r io.ReadSeeker) (bool, error) {
141+
currentPos, err := r.Seek(0, io.SeekCurrent)
142+
if err != nil {
143+
return false, err
144+
}
145+
defer r.Seek(currentPos, io.SeekStart)
146+
147+
// Read the first part of what could be a BigObjHeader
148+
var sig struct {
149+
Sig1 uint16
150+
Sig2 uint16
151+
Version uint16
152+
Machine uint16
153+
TimeDateStamp uint32
154+
ClassID [16]uint8
155+
}
156+
157+
err = binary.Read(r, binary.LittleEndian, &sig)
158+
if err != nil {
159+
return false, err
160+
}
161+
162+
if sig.Sig1 != BigObjSig1 || sig.Sig2 != BigObjSig2 {
163+
return false, nil
164+
}
165+
166+
if sig.ClassID != BigObjClassID {
167+
return false, nil
168+
}
169+
170+
return true, nil
171+
}
172+
173+
// readCOFFHeader reads the appropriate COFF header type ("regular" or bigobj).
174+
// The unused header type will be nil
175+
func readCOFFHeader(sr *io.SectionReader, base int64) (*FileHeader, *BigObjHeader, error) {
176+
_, err := sr.Seek(base, io.SeekStart)
177+
if err != nil {
178+
return nil, nil, err
179+
}
180+
181+
isBigObj, err := isBigObjFormat(sr)
182+
if err != nil {
183+
return nil, nil, err
184+
}
185+
186+
if isBigObj {
187+
bigObjHeader := new(BigObjHeader)
188+
if err := binary.Read(sr, binary.LittleEndian, bigObjHeader); err != nil {
189+
return nil, nil, err
190+
}
191+
return nil, bigObjHeader, nil
192+
} else {
193+
fileHeader := new(FileHeader)
194+
if err := binary.Read(sr, binary.LittleEndian, fileHeader); err != nil {
195+
return nil, nil, err
196+
}
197+
return fileHeader, nil, nil
198+
}
199+
}
200+
71201
// NewFile creates a new [File] for accessing a PE binary in an underlying reader.
72202
func NewFile(r io.ReaderAt) (*File, error) {
73203
f := new(File)
@@ -89,11 +219,24 @@ func NewFile(r io.ReaderAt) (*File, error) {
89219
} else {
90220
base = int64(0)
91221
}
92-
sr.Seek(base, io.SeekStart)
93-
if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
222+
// Read appropriate header type - unused header will be nil
223+
fileHeader, bigObjHeader, err := readCOFFHeader(sr, base)
224+
if err != nil {
94225
return nil, err
95226
}
96-
switch f.FileHeader.Machine {
227+
f.FileHeader = fileHeader
228+
f.BigObjHeader = bigObjHeader
229+
230+
// Calculate header size based on actual type
231+
var headerSize int
232+
if f.BigObjHeader != nil {
233+
headerSize = binary.Size(*f.BigObjHeader)
234+
} else {
235+
headerSize = binary.Size(*f.FileHeader)
236+
}
237+
238+
// Validate machine type
239+
switch f.GetMachine() {
97240
case IMAGE_FILE_MACHINE_AMD64,
98241
IMAGE_FILE_MACHINE_ARM64,
99242
IMAGE_FILE_MACHINE_ARMNT,
@@ -104,19 +247,17 @@ func NewFile(r io.ReaderAt) (*File, error) {
104247
IMAGE_FILE_MACHINE_UNKNOWN:
105248
// ok
106249
default:
107-
return nil, fmt.Errorf("unrecognized PE machine: %#x", f.FileHeader.Machine)
250+
return nil, fmt.Errorf("unrecognized PE machine: %#x", f.GetMachine())
108251
}
109252

110-
var err error
111-
112253
// Read string table.
113-
f.StringTable, err = readStringTable(&f.FileHeader, sr)
254+
f.StringTable, err = readStringTableFromFile(f, sr)
114255
if err != nil {
115256
return nil, err
116257
}
117258

118259
// Read symbol table.
119-
f.COFFSymbols, err = readCOFFSymbols(&f.FileHeader, sr)
260+
f.COFFSymbols, err = readCOFFSymbols(f, sr)
120261
if err != nil {
121262
return nil, err
122263
}
@@ -126,20 +267,23 @@ func NewFile(r io.ReaderAt) (*File, error) {
126267
}
127268

128269
// Seek past file header.
129-
_, err = sr.Seek(base+int64(binary.Size(f.FileHeader)), io.SeekStart)
270+
_, err = sr.Seek(base+int64(headerSize), io.SeekStart)
130271
if err != nil {
131272
return nil, err
132273
}
133274

134-
// Read optional header.
135-
f.OptionalHeader, err = readOptionalHeader(sr, f.FileHeader.SizeOfOptionalHeader)
136-
if err != nil {
137-
return nil, err
275+
// Read optional header (only for regular COFF files).
276+
if !f.IsBigObj() {
277+
f.OptionalHeader, err = readOptionalHeader(sr, f.GetSizeOfOptionalHeader())
278+
if err != nil {
279+
return nil, err
280+
}
138281
}
139282

140283
// Process sections.
141-
f.Sections = make([]*Section, f.FileHeader.NumberOfSections)
142-
for i := 0; i < int(f.FileHeader.NumberOfSections); i++ {
284+
numSections := f.GetNumberOfSections()
285+
f.Sections = make([]*Section, numSections)
286+
for i := uint32(0); i < numSections; i++ {
143287
sh := new(SectionHeader32)
144288
if err := binary.Read(sr, binary.LittleEndian, sh); err != nil {
145289
return nil, err

src/debug/pe/pe.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,37 @@ type FileHeader struct {
1414
Characteristics uint16
1515
}
1616

17+
// BigObjHeader represents the ANON_OBJECT_HEADER_BIGOBJ structure
18+
// used in bigobj COFF format. This format allows for more than 65535 sections.
19+
type BigObjHeader struct {
20+
Sig1 uint16 // Must be 0x0
21+
Sig2 uint16 // Must be 0xFFFF
22+
Version uint16 // Currently 2
23+
Machine uint16
24+
TimeDateStamp uint32
25+
ClassID [16]uint8 // GUID that identifies this as bigobj format
26+
SizeOfData uint32
27+
Flags uint32
28+
MetaDataSize uint32
29+
MetaDataOffset uint32
30+
NumberOfSections uint32 // 32-bit field (vs 16-bit in regular COFF)
31+
PointerToSymbolTable uint32
32+
NumberOfSymbols uint32
33+
}
34+
35+
// BigObj signature constants
36+
const (
37+
BigObjSig1 = 0x0
38+
BigObjSig2 = 0xFFFF
39+
BigObjVersion = 2
40+
)
41+
42+
// The GUID that identifies a file as bigobj format
43+
var BigObjClassID = [16]uint8{
44+
0xC7, 0xA1, 0xBA, 0xD1, 0xEE, 0xBA, 0xA9, 0x4B,
45+
0xAF, 0x20, 0xFA, 0xF6, 0x6A, 0xA4, 0xDC, 0xB8,
46+
}
47+
1748
type DataDirectory struct {
1849
VirtualAddress uint32
1950
Size uint32

src/debug/pe/string.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@ func cstring(b []byte) string {
2525
// StringTable is a COFF string table.
2626
type StringTable []byte
2727

28-
func readStringTable(fh *FileHeader, r io.ReadSeeker) (StringTable, error) {
28+
// readStringTableFromFile reads string table using the File struct
29+
func readStringTableFromFile(f *File, r io.ReadSeeker) (StringTable, error) {
2930
// COFF string table is located right after COFF symbol table.
30-
if fh.PointerToSymbolTable <= 0 {
31+
if f.GetPointerToSymbolTable() <= 0 {
3132
return nil, nil
3233
}
33-
offset := fh.PointerToSymbolTable + COFFSymbolSize*fh.NumberOfSymbols
34+
35+
var symbolSize uint32 = COFFSymbolSize
36+
if f.IsBigObj() {
37+
symbolSize = BigObjSymbolSize
38+
}
39+
40+
offset := f.GetPointerToSymbolTable() + symbolSize*f.GetNumberOfSymbols()
3441
_, err := r.Seek(int64(offset), io.SeekStart)
3542
if err != nil {
3643
return nil, fmt.Errorf("fail to seek to string table: %v", err)

0 commit comments

Comments
 (0)