diff options
Diffstat (limited to 'src/debug/pe')
-rw-r--r-- | src/debug/pe/file.go | 630 | ||||
-rw-r--r-- | src/debug/pe/file_cgo_test.go | 35 | ||||
-rw-r--r-- | src/debug/pe/file_test.go | 745 | ||||
-rw-r--r-- | src/debug/pe/pe.go | 189 | ||||
-rw-r--r-- | src/debug/pe/section.go | 119 | ||||
-rw-r--r-- | src/debug/pe/string.go | 69 | ||||
-rw-r--r-- | src/debug/pe/symbol.go | 209 | ||||
-rw-r--r-- | src/debug/pe/symbols_test.go | 91 | ||||
-rw-r--r-- | src/debug/pe/testdata/gcc-386-mingw-exec | bin | 0 -> 29941 bytes | |||
-rw-r--r-- | src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec | bin | 0 -> 8704 bytes | |||
-rw-r--r-- | src/debug/pe/testdata/gcc-386-mingw-obj | bin | 0 -> 2372 bytes | |||
-rw-r--r-- | src/debug/pe/testdata/gcc-amd64-mingw-exec | bin | 0 -> 273083 bytes | |||
-rw-r--r-- | src/debug/pe/testdata/gcc-amd64-mingw-obj | bin | 0 -> 736 bytes | |||
-rw-r--r-- | src/debug/pe/testdata/hello.c | 8 | ||||
-rw-r--r-- | src/debug/pe/testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2 | bin | 0 -> 24046 bytes | |||
-rw-r--r-- | src/debug/pe/testdata/vmlinuz-4.15.0-47-generic | bin | 0 -> 474 bytes |
16 files changed, 2095 insertions, 0 deletions
diff --git a/src/debug/pe/file.go b/src/debug/pe/file.go new file mode 100644 index 0000000..f8c922d --- /dev/null +++ b/src/debug/pe/file.go @@ -0,0 +1,630 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package pe implements access to PE (Microsoft Windows Portable Executable) files. + +# Security + +This package is not designed to be hardened against adversarial inputs, and is +outside the scope of https://go.dev/security/policy. In particular, only basic +validation is done when parsing object files. As such, care should be taken when +parsing untrusted inputs, as parsing malformed files may consume significant +resources, or cause panics. +*/ +package pe + +import ( + "bytes" + "compress/zlib" + "debug/dwarf" + "encoding/binary" + "fmt" + "io" + "os" + "strings" +) + +// Avoid use of post-Go 1.4 io features, to make safe for toolchain bootstrap. +const seekStart = 0 + +// A File represents an open PE file. +type File struct { + FileHeader + OptionalHeader any // of type *OptionalHeader32 or *OptionalHeader64 + Sections []*Section + Symbols []*Symbol // COFF symbols with auxiliary symbol records removed + COFFSymbols []COFFSymbol // all COFF symbols (including auxiliary symbol records) + StringTable StringTable + + closer io.Closer +} + +// Open opens the named file using os.Open and prepares it for use as a PE binary. +func Open(name string) (*File, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + ff, err := NewFile(f) + if err != nil { + f.Close() + return nil, err + } + ff.closer = f + return ff, nil +} + +// Close closes the File. +// If the File was created using NewFile directly instead of Open, +// Close has no effect. +func (f *File) Close() error { + var err error + if f.closer != nil { + err = f.closer.Close() + f.closer = nil + } + return err +} + +// TODO(brainman): add Load function, as a replacement for NewFile, that does not call removeAuxSymbols (for performance) + +// NewFile creates a new File for accessing a PE binary in an underlying reader. +func NewFile(r io.ReaderAt) (*File, error) { + f := new(File) + sr := io.NewSectionReader(r, 0, 1<<63-1) + + var dosheader [96]byte + if _, err := r.ReadAt(dosheader[0:], 0); err != nil { + return nil, err + } + var base int64 + if dosheader[0] == 'M' && dosheader[1] == 'Z' { + signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:])) + var sign [4]byte + r.ReadAt(sign[:], signoff) + if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) { + return nil, fmt.Errorf("invalid PE file signature: % x", sign) + } + base = signoff + 4 + } else { + base = int64(0) + } + sr.Seek(base, seekStart) + if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil { + return nil, err + } + switch f.FileHeader.Machine { + case IMAGE_FILE_MACHINE_AMD64, + IMAGE_FILE_MACHINE_ARM64, + IMAGE_FILE_MACHINE_ARMNT, + IMAGE_FILE_MACHINE_I386, + IMAGE_FILE_MACHINE_RISCV32, + IMAGE_FILE_MACHINE_RISCV64, + IMAGE_FILE_MACHINE_RISCV128, + IMAGE_FILE_MACHINE_UNKNOWN: + // ok + default: + return nil, fmt.Errorf("unrecognized PE machine: %#x", f.FileHeader.Machine) + } + + var err error + + // Read string table. + f.StringTable, err = readStringTable(&f.FileHeader, sr) + if err != nil { + return nil, err + } + + // Read symbol table. + f.COFFSymbols, err = readCOFFSymbols(&f.FileHeader, sr) + if err != nil { + return nil, err + } + f.Symbols, err = removeAuxSymbols(f.COFFSymbols, f.StringTable) + if err != nil { + return nil, err + } + + // Seek past file header. + _, err = sr.Seek(base+int64(binary.Size(f.FileHeader)), seekStart) + if err != nil { + return nil, err + } + + // Read optional header. + f.OptionalHeader, err = readOptionalHeader(sr, f.FileHeader.SizeOfOptionalHeader) + if err != nil { + return nil, err + } + + // Process sections. + f.Sections = make([]*Section, f.FileHeader.NumberOfSections) + for i := 0; i < int(f.FileHeader.NumberOfSections); i++ { + sh := new(SectionHeader32) + if err := binary.Read(sr, binary.LittleEndian, sh); err != nil { + return nil, err + } + name, err := sh.fullName(f.StringTable) + if err != nil { + return nil, err + } + s := new(Section) + s.SectionHeader = SectionHeader{ + Name: name, + VirtualSize: sh.VirtualSize, + VirtualAddress: sh.VirtualAddress, + Size: sh.SizeOfRawData, + Offset: sh.PointerToRawData, + PointerToRelocations: sh.PointerToRelocations, + PointerToLineNumbers: sh.PointerToLineNumbers, + NumberOfRelocations: sh.NumberOfRelocations, + NumberOfLineNumbers: sh.NumberOfLineNumbers, + Characteristics: sh.Characteristics, + } + r2 := r + if sh.PointerToRawData == 0 { // .bss must have all 0s + r2 = zeroReaderAt{} + } + s.sr = io.NewSectionReader(r2, int64(s.SectionHeader.Offset), int64(s.SectionHeader.Size)) + s.ReaderAt = s.sr + f.Sections[i] = s + } + for i := range f.Sections { + var err error + f.Sections[i].Relocs, err = readRelocs(&f.Sections[i].SectionHeader, sr) + if err != nil { + return nil, err + } + } + + return f, nil +} + +// zeroReaderAt is ReaderAt that reads 0s. +type zeroReaderAt struct{} + +// ReadAt writes len(p) 0s into p. +func (w zeroReaderAt) ReadAt(p []byte, off int64) (n int, err error) { + for i := range p { + p[i] = 0 + } + return len(p), nil +} + +// getString extracts a string from symbol string table. +func getString(section []byte, start int) (string, bool) { + if start < 0 || start >= len(section) { + return "", false + } + + for end := start; end < len(section); end++ { + if section[end] == 0 { + return string(section[start:end]), true + } + } + return "", false +} + +// Section returns the first section with the given name, or nil if no such +// section exists. +func (f *File) Section(name string) *Section { + for _, s := range f.Sections { + if s.Name == name { + return s + } + } + return nil +} + +func (f *File) DWARF() (*dwarf.Data, error) { + dwarfSuffix := func(s *Section) string { + switch { + case strings.HasPrefix(s.Name, ".debug_"): + return s.Name[7:] + case strings.HasPrefix(s.Name, ".zdebug_"): + return s.Name[8:] + default: + return "" + } + + } + + // sectionData gets the data for s and checks its size. + sectionData := func(s *Section) ([]byte, error) { + b, err := s.Data() + if err != nil && uint32(len(b)) < s.Size { + return nil, err + } + + if 0 < s.VirtualSize && s.VirtualSize < s.Size { + b = b[:s.VirtualSize] + } + + if len(b) >= 12 && string(b[:4]) == "ZLIB" { + dlen := binary.BigEndian.Uint64(b[4:12]) + dbuf := make([]byte, dlen) + r, err := zlib.NewReader(bytes.NewBuffer(b[12:])) + if err != nil { + return nil, err + } + if _, err := io.ReadFull(r, dbuf); err != nil { + return nil, err + } + if err := r.Close(); err != nil { + return nil, err + } + b = dbuf + } + return b, nil + } + + // There are many other DWARF sections, but these + // are the ones the debug/dwarf package uses. + // Don't bother loading others. + var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil} + for _, s := range f.Sections { + suffix := dwarfSuffix(s) + if suffix == "" { + continue + } + if _, ok := dat[suffix]; !ok { + continue + } + + b, err := sectionData(s) + if err != nil { + return nil, err + } + dat[suffix] = b + } + + d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"]) + if err != nil { + return nil, err + } + + // Look for DWARF4 .debug_types sections and DWARF5 sections. + for i, s := range f.Sections { + suffix := dwarfSuffix(s) + if suffix == "" { + continue + } + if _, ok := dat[suffix]; ok { + // Already handled. + continue + } + + b, err := sectionData(s) + if err != nil { + return nil, err + } + + if suffix == "types" { + err = d.AddTypes(fmt.Sprintf("types-%d", i), b) + } else { + err = d.AddSection(".debug_"+suffix, b) + } + if err != nil { + return nil, err + } + } + + return d, nil +} + +// TODO(brainman): document ImportDirectory once we decide what to do with it. + +type ImportDirectory struct { + OriginalFirstThunk uint32 + TimeDateStamp uint32 + ForwarderChain uint32 + Name uint32 + FirstThunk uint32 + + dll string +} + +// ImportedSymbols returns the names of all symbols +// referred to by the binary f that are expected to be +// satisfied by other libraries at dynamic load time. +// It does not return weak symbols. +func (f *File) ImportedSymbols() ([]string, error) { + if f.OptionalHeader == nil { + return nil, nil + } + + _, pe64 := f.OptionalHeader.(*OptionalHeader64) + + // grab the number of data directory entries + var dd_length uint32 + if pe64 { + dd_length = f.OptionalHeader.(*OptionalHeader64).NumberOfRvaAndSizes + } else { + dd_length = f.OptionalHeader.(*OptionalHeader32).NumberOfRvaAndSizes + } + + // check that the length of data directory entries is large + // enough to include the imports directory. + if dd_length < IMAGE_DIRECTORY_ENTRY_IMPORT+1 { + return nil, nil + } + + // grab the import data directory entry + var idd DataDirectory + if pe64 { + idd = f.OptionalHeader.(*OptionalHeader64).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] + } else { + idd = f.OptionalHeader.(*OptionalHeader32).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] + } + + // figure out which section contains the import directory table + var ds *Section + ds = nil + for _, s := range f.Sections { + // We are using distance between s.VirtualAddress and idd.VirtualAddress + // to avoid potential overflow of uint32 caused by addition of s.VirtualSize + // to s.VirtualAddress. + if s.VirtualAddress <= idd.VirtualAddress && idd.VirtualAddress-s.VirtualAddress < s.VirtualSize { + ds = s + break + } + } + + // didn't find a section, so no import libraries were found + if ds == nil { + return nil, nil + } + + d, err := ds.Data() + if err != nil { + return nil, err + } + + // seek to the virtual address specified in the import data directory + d = d[idd.VirtualAddress-ds.VirtualAddress:] + + // start decoding the import directory + var ida []ImportDirectory + for len(d) >= 20 { + var dt ImportDirectory + dt.OriginalFirstThunk = binary.LittleEndian.Uint32(d[0:4]) + dt.TimeDateStamp = binary.LittleEndian.Uint32(d[4:8]) + dt.ForwarderChain = binary.LittleEndian.Uint32(d[8:12]) + dt.Name = binary.LittleEndian.Uint32(d[12:16]) + dt.FirstThunk = binary.LittleEndian.Uint32(d[16:20]) + d = d[20:] + if dt.OriginalFirstThunk == 0 { + break + } + ida = append(ida, dt) + } + // TODO(brainman): this needs to be rewritten + // ds.Data() returns contents of section containing import table. Why store in variable called "names"? + // Why we are retrieving it second time? We already have it in "d", and it is not modified anywhere. + // getString does not extracts a string from symbol string table (as getString doco says). + // Why ds.Data() called again and again in the loop? + // Needs test before rewrite. + names, _ := ds.Data() + var all []string + for _, dt := range ida { + dt.dll, _ = getString(names, int(dt.Name-ds.VirtualAddress)) + d, _ = ds.Data() + // seek to OriginalFirstThunk + d = d[dt.OriginalFirstThunk-ds.VirtualAddress:] + for len(d) > 0 { + if pe64 { // 64bit + va := binary.LittleEndian.Uint64(d[0:8]) + d = d[8:] + if va == 0 { + break + } + if va&0x8000000000000000 > 0 { // is Ordinal + // TODO add dynimport ordinal support. + } else { + fn, _ := getString(names, int(uint32(va)-ds.VirtualAddress+2)) + all = append(all, fn+":"+dt.dll) + } + } else { // 32bit + va := binary.LittleEndian.Uint32(d[0:4]) + d = d[4:] + if va == 0 { + break + } + if va&0x80000000 > 0 { // is Ordinal + // TODO add dynimport ordinal support. + //ord := va&0x0000FFFF + } else { + fn, _ := getString(names, int(va-ds.VirtualAddress+2)) + all = append(all, fn+":"+dt.dll) + } + } + } + } + + return all, nil +} + +// ImportedLibraries returns the names of all libraries +// referred to by the binary f that are expected to be +// linked with the binary at dynamic link time. +func (f *File) ImportedLibraries() ([]string, error) { + // TODO + // cgo -dynimport don't use this for windows PE, so just return. + return nil, nil +} + +// FormatError is unused. +// The type is retained for compatibility. +type FormatError struct { +} + +func (e *FormatError) Error() string { + return "unknown error" +} + +// readOptionalHeader accepts a io.ReadSeeker pointing to optional header in the PE file +// and its size as seen in the file header. +// It parses the given size of bytes and returns optional header. It infers whether the +// bytes being parsed refer to 32 bit or 64 bit version of optional header. +func readOptionalHeader(r io.ReadSeeker, sz uint16) (any, error) { + // If optional header size is 0, return empty optional header. + if sz == 0 { + return nil, nil + } + + var ( + // First couple of bytes in option header state its type. + // We need to read them first to determine the type and + // validity of optional header. + ohMagic uint16 + ohMagicSz = binary.Size(ohMagic) + ) + + // If optional header size is greater than 0 but less than its magic size, return error. + if sz < uint16(ohMagicSz) { + return nil, fmt.Errorf("optional header size is less than optional header magic size") + } + + // read reads from io.ReadSeeke, r, into data. + var err error + read := func(data any) bool { + err = binary.Read(r, binary.LittleEndian, data) + return err == nil + } + + if !read(&ohMagic) { + return nil, fmt.Errorf("failure to read optional header magic: %v", err) + + } + + switch ohMagic { + case 0x10b: // PE32 + var ( + oh32 OptionalHeader32 + // There can be 0 or more data directories. So the minimum size of optional + // header is calculated by subtracting oh32.DataDirectory size from oh32 size. + oh32MinSz = binary.Size(oh32) - binary.Size(oh32.DataDirectory) + ) + + if sz < uint16(oh32MinSz) { + return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) of PE32 optional header", sz, oh32MinSz) + } + + // Init oh32 fields + oh32.Magic = ohMagic + if !read(&oh32.MajorLinkerVersion) || + !read(&oh32.MinorLinkerVersion) || + !read(&oh32.SizeOfCode) || + !read(&oh32.SizeOfInitializedData) || + !read(&oh32.SizeOfUninitializedData) || + !read(&oh32.AddressOfEntryPoint) || + !read(&oh32.BaseOfCode) || + !read(&oh32.BaseOfData) || + !read(&oh32.ImageBase) || + !read(&oh32.SectionAlignment) || + !read(&oh32.FileAlignment) || + !read(&oh32.MajorOperatingSystemVersion) || + !read(&oh32.MinorOperatingSystemVersion) || + !read(&oh32.MajorImageVersion) || + !read(&oh32.MinorImageVersion) || + !read(&oh32.MajorSubsystemVersion) || + !read(&oh32.MinorSubsystemVersion) || + !read(&oh32.Win32VersionValue) || + !read(&oh32.SizeOfImage) || + !read(&oh32.SizeOfHeaders) || + !read(&oh32.CheckSum) || + !read(&oh32.Subsystem) || + !read(&oh32.DllCharacteristics) || + !read(&oh32.SizeOfStackReserve) || + !read(&oh32.SizeOfStackCommit) || + !read(&oh32.SizeOfHeapReserve) || + !read(&oh32.SizeOfHeapCommit) || + !read(&oh32.LoaderFlags) || + !read(&oh32.NumberOfRvaAndSizes) { + return nil, fmt.Errorf("failure to read PE32 optional header: %v", err) + } + + dd, err := readDataDirectories(r, sz-uint16(oh32MinSz), oh32.NumberOfRvaAndSizes) + if err != nil { + return nil, err + } + + copy(oh32.DataDirectory[:], dd) + + return &oh32, nil + case 0x20b: // PE32+ + var ( + oh64 OptionalHeader64 + // There can be 0 or more data directories. So the minimum size of optional + // header is calculated by subtracting oh64.DataDirectory size from oh64 size. + oh64MinSz = binary.Size(oh64) - binary.Size(oh64.DataDirectory) + ) + + if sz < uint16(oh64MinSz) { + return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) for PE32+ optional header", sz, oh64MinSz) + } + + // Init oh64 fields + oh64.Magic = ohMagic + if !read(&oh64.MajorLinkerVersion) || + !read(&oh64.MinorLinkerVersion) || + !read(&oh64.SizeOfCode) || + !read(&oh64.SizeOfInitializedData) || + !read(&oh64.SizeOfUninitializedData) || + !read(&oh64.AddressOfEntryPoint) || + !read(&oh64.BaseOfCode) || + !read(&oh64.ImageBase) || + !read(&oh64.SectionAlignment) || + !read(&oh64.FileAlignment) || + !read(&oh64.MajorOperatingSystemVersion) || + !read(&oh64.MinorOperatingSystemVersion) || + !read(&oh64.MajorImageVersion) || + !read(&oh64.MinorImageVersion) || + !read(&oh64.MajorSubsystemVersion) || + !read(&oh64.MinorSubsystemVersion) || + !read(&oh64.Win32VersionValue) || + !read(&oh64.SizeOfImage) || + !read(&oh64.SizeOfHeaders) || + !read(&oh64.CheckSum) || + !read(&oh64.Subsystem) || + !read(&oh64.DllCharacteristics) || + !read(&oh64.SizeOfStackReserve) || + !read(&oh64.SizeOfStackCommit) || + !read(&oh64.SizeOfHeapReserve) || + !read(&oh64.SizeOfHeapCommit) || + !read(&oh64.LoaderFlags) || + !read(&oh64.NumberOfRvaAndSizes) { + return nil, fmt.Errorf("failure to read PE32+ optional header: %v", err) + } + + dd, err := readDataDirectories(r, sz-uint16(oh64MinSz), oh64.NumberOfRvaAndSizes) + if err != nil { + return nil, err + } + + copy(oh64.DataDirectory[:], dd) + + return &oh64, nil + default: + return nil, fmt.Errorf("optional header has unexpected Magic of 0x%x", ohMagic) + } +} + +// readDataDirectories accepts a io.ReadSeeker pointing to data directories in the PE file, +// its size and number of data directories as seen in optional header. +// It parses the given size of bytes and returns given number of data directories. +func readDataDirectories(r io.ReadSeeker, sz uint16, n uint32) ([]DataDirectory, error) { + ddSz := uint64(binary.Size(DataDirectory{})) + if uint64(sz) != uint64(n)*ddSz { + return nil, fmt.Errorf("size of data directories(%d) is inconsistent with number of data directories(%d)", sz, n) + } + + dd := make([]DataDirectory, n) + if err := binary.Read(r, binary.LittleEndian, dd); err != nil { + return nil, fmt.Errorf("failure to read data directories: %v", err) + } + + return dd, nil +} diff --git a/src/debug/pe/file_cgo_test.go b/src/debug/pe/file_cgo_test.go new file mode 100644 index 0000000..9280de1 --- /dev/null +++ b/src/debug/pe/file_cgo_test.go @@ -0,0 +1,35 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build cgo + +package pe + +import ( + "os/exec" + "runtime" + "testing" +) + +func testCgoDWARF(t *testing.T, linktype int) { + if _, err := exec.LookPath("gcc"); err != nil { + t.Skip("skipping test: gcc is missing") + } + testDWARF(t, linktype) +} + +func TestDefaultLinkerDWARF(t *testing.T) { + testCgoDWARF(t, linkCgoDefault) +} + +func TestInternalLinkerDWARF(t *testing.T) { + if runtime.GOARCH == "arm64" { + t.Skip("internal linker disabled on windows/arm64") + } + testCgoDWARF(t, linkCgoInternal) +} + +func TestExternalLinkerDWARF(t *testing.T) { + testCgoDWARF(t, linkCgoExternal) +} diff --git a/src/debug/pe/file_test.go b/src/debug/pe/file_test.go new file mode 100644 index 0000000..5368e08 --- /dev/null +++ b/src/debug/pe/file_test.go @@ -0,0 +1,745 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pe + +import ( + "bytes" + "debug/dwarf" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strconv" + "testing" + "text/template" +) + +type fileTest struct { + file string + hdr FileHeader + opthdr any + sections []*SectionHeader + symbols []*Symbol + hasNoDwarfInfo bool +} + +var fileTests = []fileTest{ + { + file: "testdata/gcc-386-mingw-obj", + hdr: FileHeader{0x014c, 0x000c, 0x0, 0x64a, 0x1e, 0x0, 0x104}, + sections: []*SectionHeader{ + {".text", 0, 0, 36, 500, 1440, 0, 3, 0, 0x60300020}, + {".data", 0, 0, 0, 0, 0, 0, 0, 0, 3224371264}, + {".bss", 0, 0, 0, 0, 0, 0, 0, 0, 3224371328}, + {".debug_abbrev", 0, 0, 137, 536, 0, 0, 0, 0, 0x42100000}, + {".debug_info", 0, 0, 418, 673, 1470, 0, 7, 0, 1108344832}, + {".debug_line", 0, 0, 128, 1091, 1540, 0, 1, 0, 1108344832}, + {".rdata", 0, 0, 16, 1219, 0, 0, 0, 0, 1076887616}, + {".debug_frame", 0, 0, 52, 1235, 1550, 0, 2, 0, 1110441984}, + {".debug_loc", 0, 0, 56, 1287, 0, 0, 0, 0, 1108344832}, + {".debug_pubnames", 0, 0, 27, 1343, 1570, 0, 1, 0, 1108344832}, + {".debug_pubtypes", 0, 0, 38, 1370, 1580, 0, 1, 0, 1108344832}, + {".debug_aranges", 0, 0, 32, 1408, 1590, 0, 2, 0, 1108344832}, + }, + symbols: []*Symbol{ + {".file", 0x0, -2, 0x0, 0x67}, + {"_main", 0x0, 1, 0x20, 0x2}, + {".text", 0x0, 1, 0x0, 0x3}, + {".data", 0x0, 2, 0x0, 0x3}, + {".bss", 0x0, 3, 0x0, 0x3}, + {".debug_abbrev", 0x0, 4, 0x0, 0x3}, + {".debug_info", 0x0, 5, 0x0, 0x3}, + {".debug_line", 0x0, 6, 0x0, 0x3}, + {".rdata", 0x0, 7, 0x0, 0x3}, + {".debug_frame", 0x0, 8, 0x0, 0x3}, + {".debug_loc", 0x0, 9, 0x0, 0x3}, + {".debug_pubnames", 0x0, 10, 0x0, 0x3}, + {".debug_pubtypes", 0x0, 11, 0x0, 0x3}, + {".debug_aranges", 0x0, 12, 0x0, 0x3}, + {"___main", 0x0, 0, 0x20, 0x2}, + {"_puts", 0x0, 0, 0x20, 0x2}, + }, + }, + { + file: "testdata/gcc-386-mingw-exec", + hdr: FileHeader{0x014c, 0x000f, 0x4c6a1b60, 0x3c00, 0x282, 0xe0, 0x107}, + opthdr: &OptionalHeader32{ + 0x10b, 0x2, 0x38, 0xe00, 0x1a00, 0x200, 0x1160, 0x1000, 0x2000, 0x400000, 0x1000, 0x200, 0x4, 0x0, 0x1, 0x0, 0x4, 0x0, 0x0, 0x10000, 0x400, 0x14abb, 0x3, 0x0, 0x200000, 0x1000, 0x100000, 0x1000, 0x0, 0x10, + [16]DataDirectory{ + {0x0, 0x0}, + {0x5000, 0x3c8}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x7000, 0x18}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + }, + }, + sections: []*SectionHeader{ + {".text", 0xcd8, 0x1000, 0xe00, 0x400, 0x0, 0x0, 0x0, 0x0, 0x60500060}, + {".data", 0x10, 0x2000, 0x200, 0x1200, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".rdata", 0x120, 0x3000, 0x200, 0x1400, 0x0, 0x0, 0x0, 0x0, 0x40300040}, + {".bss", 0xdc, 0x4000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0400080}, + {".idata", 0x3c8, 0x5000, 0x400, 0x1600, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".CRT", 0x18, 0x6000, 0x200, 0x1a00, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".tls", 0x20, 0x7000, 0x200, 0x1c00, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".debug_aranges", 0x20, 0x8000, 0x200, 0x1e00, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + {".debug_pubnames", 0x51, 0x9000, 0x200, 0x2000, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + {".debug_pubtypes", 0x91, 0xa000, 0x200, 0x2200, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + {".debug_info", 0xe22, 0xb000, 0x1000, 0x2400, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + {".debug_abbrev", 0x157, 0xc000, 0x200, 0x3400, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + {".debug_line", 0x144, 0xd000, 0x200, 0x3600, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + {".debug_frame", 0x34, 0xe000, 0x200, 0x3800, 0x0, 0x0, 0x0, 0x0, 0x42300000}, + {".debug_loc", 0x38, 0xf000, 0x200, 0x3a00, 0x0, 0x0, 0x0, 0x0, 0x42100000}, + }, + }, + { + file: "testdata/gcc-386-mingw-no-symbols-exec", + hdr: FileHeader{0x14c, 0x8, 0x69676572, 0x0, 0x0, 0xe0, 0x30f}, + opthdr: &OptionalHeader32{0x10b, 0x2, 0x18, 0xe00, 0x1e00, 0x200, 0x1280, 0x1000, 0x2000, 0x400000, 0x1000, 0x200, 0x4, 0x0, 0x1, 0x0, 0x4, 0x0, 0x0, 0x9000, 0x400, 0x5306, 0x3, 0x0, 0x200000, 0x1000, 0x100000, 0x1000, 0x0, 0x10, + [16]DataDirectory{ + {0x0, 0x0}, + {0x6000, 0x378}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x8004, 0x18}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x60b8, 0x7c}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + }, + }, + sections: []*SectionHeader{ + {".text", 0xc64, 0x1000, 0xe00, 0x400, 0x0, 0x0, 0x0, 0x0, 0x60500060}, + {".data", 0x10, 0x2000, 0x200, 0x1200, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".rdata", 0x134, 0x3000, 0x200, 0x1400, 0x0, 0x0, 0x0, 0x0, 0x40300040}, + {".eh_fram", 0x3a0, 0x4000, 0x400, 0x1600, 0x0, 0x0, 0x0, 0x0, 0x40300040}, + {".bss", 0x60, 0x5000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0300080}, + {".idata", 0x378, 0x6000, 0x400, 0x1a00, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".CRT", 0x18, 0x7000, 0x200, 0x1e00, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".tls", 0x20, 0x8000, 0x200, 0x2000, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + }, + hasNoDwarfInfo: true, + }, + { + file: "testdata/gcc-amd64-mingw-obj", + hdr: FileHeader{0x8664, 0x6, 0x0, 0x198, 0x12, 0x0, 0x4}, + sections: []*SectionHeader{ + {".text", 0x0, 0x0, 0x30, 0x104, 0x15c, 0x0, 0x3, 0x0, 0x60500020}, + {".data", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0500040}, + {".bss", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0500080}, + {".rdata", 0x0, 0x0, 0x10, 0x134, 0x0, 0x0, 0x0, 0x0, 0x40500040}, + {".xdata", 0x0, 0x0, 0xc, 0x144, 0x0, 0x0, 0x0, 0x0, 0x40300040}, + {".pdata", 0x0, 0x0, 0xc, 0x150, 0x17a, 0x0, 0x3, 0x0, 0x40300040}, + }, + symbols: []*Symbol{ + {".file", 0x0, -2, 0x0, 0x67}, + {"main", 0x0, 1, 0x20, 0x2}, + {".text", 0x0, 1, 0x0, 0x3}, + {".data", 0x0, 2, 0x0, 0x3}, + {".bss", 0x0, 3, 0x0, 0x3}, + {".rdata", 0x0, 4, 0x0, 0x3}, + {".xdata", 0x0, 5, 0x0, 0x3}, + {".pdata", 0x0, 6, 0x0, 0x3}, + {"__main", 0x0, 0, 0x20, 0x2}, + {"puts", 0x0, 0, 0x20, 0x2}, + }, + hasNoDwarfInfo: true, + }, + { + file: "testdata/gcc-amd64-mingw-exec", + hdr: FileHeader{0x8664, 0x11, 0x53e4364f, 0x39600, 0x6fc, 0xf0, 0x27}, + opthdr: &OptionalHeader64{ + 0x20b, 0x2, 0x16, 0x6a00, 0x2400, 0x1600, 0x14e0, 0x1000, 0x400000, 0x1000, 0x200, 0x4, 0x0, 0x0, 0x0, 0x5, 0x2, 0x0, 0x45000, 0x600, 0x46f19, 0x3, 0x0, 0x200000, 0x1000, 0x100000, 0x1000, 0x0, 0x10, + [16]DataDirectory{ + {0x0, 0x0}, + {0xe000, 0x990}, + {0x0, 0x0}, + {0xa000, 0x498}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x10000, 0x28}, + {0x0, 0x0}, + {0x0, 0x0}, + {0xe254, 0x218}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + }}, + sections: []*SectionHeader{ + {".text", 0x6860, 0x1000, 0x6a00, 0x600, 0x0, 0x0, 0x0, 0x0, 0x60500020}, + {".data", 0xe0, 0x8000, 0x200, 0x7000, 0x0, 0x0, 0x0, 0x0, 0xc0500040}, + {".rdata", 0x6b0, 0x9000, 0x800, 0x7200, 0x0, 0x0, 0x0, 0x0, 0x40600040}, + {".pdata", 0x498, 0xa000, 0x600, 0x7a00, 0x0, 0x0, 0x0, 0x0, 0x40300040}, + {".xdata", 0x488, 0xb000, 0x600, 0x8000, 0x0, 0x0, 0x0, 0x0, 0x40300040}, + {".bss", 0x1410, 0xc000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0600080}, + {".idata", 0x990, 0xe000, 0xa00, 0x8600, 0x0, 0x0, 0x0, 0x0, 0xc0300040}, + {".CRT", 0x68, 0xf000, 0x200, 0x9000, 0x0, 0x0, 0x0, 0x0, 0xc0400040}, + {".tls", 0x48, 0x10000, 0x200, 0x9200, 0x0, 0x0, 0x0, 0x0, 0xc0600040}, + {".debug_aranges", 0x600, 0x11000, 0x600, 0x9400, 0x0, 0x0, 0x0, 0x0, 0x42500040}, + {".debug_info", 0x1316e, 0x12000, 0x13200, 0x9a00, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + {".debug_abbrev", 0x2ccb, 0x26000, 0x2e00, 0x1cc00, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + {".debug_line", 0x3c4d, 0x29000, 0x3e00, 0x1fa00, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + {".debug_frame", 0x18b8, 0x2d000, 0x1a00, 0x23800, 0x0, 0x0, 0x0, 0x0, 0x42400040}, + {".debug_str", 0x396, 0x2f000, 0x400, 0x25200, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + {".debug_loc", 0x13240, 0x30000, 0x13400, 0x25600, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + {".debug_ranges", 0xa70, 0x44000, 0xc00, 0x38a00, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + }, + }, + { + // testdata/vmlinuz-4.15.0-47-generic is a trimmed down version of Linux Kernel image. + // The original Linux Kernel image is about 8M and it is not recommended to add such a big binary file to the repo. + // Moreover only a very small portion of the original Kernel image was being parsed by debug/pe package. + // In order to identify this portion, the original image was first parsed by modified debug/pe package. + // Modification essentially communicated reader's positions before and after parsing. + // Finally, bytes between those positions where written to a separate file, + // generating trimmed down version Linux Kernel image used in this test case. + file: "testdata/vmlinuz-4.15.0-47-generic", + hdr: FileHeader{0x8664, 0x4, 0x0, 0x0, 0x1, 0xa0, 0x206}, + opthdr: &OptionalHeader64{ + 0x20b, 0x2, 0x14, 0x7c0590, 0x0, 0x168f870, 0x4680, 0x200, 0x0, 0x20, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e50000, 0x200, 0x7c3ab0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, + [16]DataDirectory{ + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x7c07a0, 0x778}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + {0x0, 0x0}, + }}, + sections: []*SectionHeader{ + {".setup", 0x41e0, 0x200, 0x41e0, 0x200, 0x0, 0x0, 0x0, 0x0, 0x60500020}, + {".reloc", 0x20, 0x43e0, 0x20, 0x43e0, 0x0, 0x0, 0x0, 0x0, 0x42100040}, + {".text", 0x7bc390, 0x4400, 0x7bc390, 0x4400, 0x0, 0x0, 0x0, 0x0, 0x60500020}, + {".bss", 0x168f870, 0x7c0790, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc8000080}, + }, + hasNoDwarfInfo: true, + }, +} + +func isOptHdrEq(a, b any) bool { + switch va := a.(type) { + case *OptionalHeader32: + vb, ok := b.(*OptionalHeader32) + if !ok { + return false + } + return *vb == *va + case *OptionalHeader64: + vb, ok := b.(*OptionalHeader64) + if !ok { + return false + } + return *vb == *va + case nil: + return b == nil + } + return false +} + +func TestOpen(t *testing.T) { + for i := range fileTests { + tt := &fileTests[i] + + f, err := Open(tt.file) + if err != nil { + t.Error(err) + continue + } + if !reflect.DeepEqual(f.FileHeader, tt.hdr) { + t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr) + continue + } + if !isOptHdrEq(tt.opthdr, f.OptionalHeader) { + t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.OptionalHeader, tt.opthdr) + continue + } + + for i, sh := range f.Sections { + if i >= len(tt.sections) { + break + } + have := &sh.SectionHeader + want := tt.sections[i] + if !reflect.DeepEqual(have, want) { + t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want) + } + } + tn := len(tt.sections) + fn := len(f.Sections) + if tn != fn { + t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn) + } + for i, have := range f.Symbols { + if i >= len(tt.symbols) { + break + } + want := tt.symbols[i] + if !reflect.DeepEqual(have, want) { + t.Errorf("open %s, symbol %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want) + } + } + if !tt.hasNoDwarfInfo { + _, err = f.DWARF() + if err != nil { + t.Errorf("fetching %s dwarf details failed: %v", tt.file, err) + } + } + } +} + +func TestOpenFailure(t *testing.T) { + filename := "file.go" // not a PE file + _, err := Open(filename) // don't crash + if err == nil { + t.Errorf("open %s: succeeded unexpectedly", filename) + } +} + +const ( + linkNoCgo = iota + linkCgoDefault + linkCgoInternal + linkCgoExternal +) + +func getImageBase(f *File) uintptr { + switch oh := f.OptionalHeader.(type) { + case *OptionalHeader32: + return uintptr(oh.ImageBase) + case *OptionalHeader64: + return uintptr(oh.ImageBase) + default: + panic("unexpected optionalheader type") + } +} + +func testDWARF(t *testing.T, linktype int) { + if runtime.GOOS != "windows" { + t.Skip("skipping windows only test") + } + testenv.MustHaveGoRun(t) + + tmpdir := t.TempDir() + + src := filepath.Join(tmpdir, "a.go") + file, err := os.Create(src) + if err != nil { + t.Fatal(err) + } + err = template.Must(template.New("main").Parse(testprog)).Execute(file, linktype != linkNoCgo) + if err != nil { + if err := file.Close(); err != nil { + t.Error(err) + } + t.Fatal(err) + } + if err := file.Close(); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "a.exe") + args := []string{"build", "-o", exe} + switch linktype { + case linkNoCgo: + case linkCgoDefault: + case linkCgoInternal: + args = append(args, "-ldflags", "-linkmode=internal") + case linkCgoExternal: + args = append(args, "-ldflags", "-linkmode=external") + default: + t.Fatalf("invalid linktype parameter of %v", linktype) + } + args = append(args, src) + out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput() + if err != nil { + t.Fatalf("building test executable for linktype %d failed: %s %s", linktype, err, out) + } + out, err = exec.Command(exe).CombinedOutput() + if err != nil { + t.Fatalf("running test executable failed: %s %s", err, out) + } + t.Logf("Testprog output:\n%s", string(out)) + + matches := regexp.MustCompile("offset=(.*)\n").FindStringSubmatch(string(out)) + if len(matches) < 2 { + t.Fatalf("unexpected program output: %s", out) + } + wantoffset, err := strconv.ParseUint(matches[1], 0, 64) + if err != nil { + t.Fatalf("unexpected main offset %q: %s", matches[1], err) + } + + f, err := Open(exe) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + imageBase := getImageBase(f) + + var foundDebugGDBScriptsSection bool + for _, sect := range f.Sections { + if sect.Name == ".debug_gdb_scripts" { + foundDebugGDBScriptsSection = true + } + } + if !foundDebugGDBScriptsSection { + t.Error(".debug_gdb_scripts section is not found") + } + + d, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + + // look for main.main + r := d.Reader() + for { + e, err := r.Next() + if err != nil { + t.Fatal("r.Next:", err) + } + if e == nil { + break + } + if e.Tag == dwarf.TagSubprogram { + name, ok := e.Val(dwarf.AttrName).(string) + if ok && name == "main.main" { + t.Logf("Found main.main") + addr, ok := e.Val(dwarf.AttrLowpc).(uint64) + if !ok { + t.Fatal("Failed to get AttrLowpc") + } + offset := uintptr(addr) - imageBase + if offset != uintptr(wantoffset) { + t.Fatalf("Runtime offset (0x%x) did "+ + "not match dwarf offset "+ + "(0x%x)", wantoffset, offset) + } + return + } + } + } + t.Fatal("main.main not found") +} + +func TestBSSHasZeros(t *testing.T) { + testenv.MustHaveExec(t) + + if runtime.GOOS != "windows" { + t.Skip("skipping windows only test") + } + gccpath, err := exec.LookPath("gcc") + if err != nil { + t.Skip("skipping test: gcc is missing") + } + + tmpdir := t.TempDir() + + srcpath := filepath.Join(tmpdir, "a.c") + src := ` +#include <stdio.h> + +int zero = 0; + +int +main(void) +{ + printf("%d\n", zero); + return 0; +} +` + err = os.WriteFile(srcpath, []byte(src), 0644) + if err != nil { + t.Fatal(err) + } + + objpath := filepath.Join(tmpdir, "a.obj") + cmd := exec.Command(gccpath, "-c", srcpath, "-o", objpath) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("failed to build object file: %v - %v", err, string(out)) + } + + f, err := Open(objpath) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + var bss *Section + for _, sect := range f.Sections { + if sect.Name == ".bss" { + bss = sect + break + } + } + if bss == nil { + t.Fatal("could not find .bss section") + } + data, err := bss.Data() + if err != nil { + t.Fatal(err) + } + if len(data) == 0 { + t.Fatalf("%s file .bss section cannot be empty", objpath) + } + for _, b := range data { + if b != 0 { + t.Fatalf(".bss section has non zero bytes: %v", data) + } + } +} + +func TestDWARF(t *testing.T) { + testDWARF(t, linkNoCgo) +} + +const testprog = ` +package main + +import "fmt" +import "syscall" +import "unsafe" +{{if .}}import "C" +{{end}} + +// struct MODULEINFO from the Windows SDK +type moduleinfo struct { + BaseOfDll uintptr + SizeOfImage uint32 + EntryPoint uintptr +} + +func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(p) + x) +} + +func funcPC(f interface{}) uintptr { + var a uintptr + return **(**uintptr)(add(unsafe.Pointer(&f), unsafe.Sizeof(a))) +} + +func main() { + kernel32 := syscall.MustLoadDLL("kernel32.dll") + psapi := syscall.MustLoadDLL("psapi.dll") + getModuleHandle := kernel32.MustFindProc("GetModuleHandleW") + getCurrentProcess := kernel32.MustFindProc("GetCurrentProcess") + getModuleInformation := psapi.MustFindProc("GetModuleInformation") + + procHandle, _, _ := getCurrentProcess.Call() + moduleHandle, _, err := getModuleHandle.Call(0) + if moduleHandle == 0 { + panic(fmt.Sprintf("GetModuleHandle() failed: %d", err)) + } + + var info moduleinfo + ret, _, err := getModuleInformation.Call(procHandle, moduleHandle, + uintptr(unsafe.Pointer(&info)), unsafe.Sizeof(info)) + + if ret == 0 { + panic(fmt.Sprintf("GetModuleInformation() failed: %d", err)) + } + + offset := funcPC(main) - info.BaseOfDll + fmt.Printf("base=0x%x\n", info.BaseOfDll) + fmt.Printf("main=%p\n", main) + fmt.Printf("offset=0x%x\n", offset) +} +` + +func TestBuildingWindowsGUI(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if runtime.GOOS != "windows" { + t.Skip("skipping windows only test") + } + tmpdir := t.TempDir() + + src := filepath.Join(tmpdir, "a.go") + if err := os.WriteFile(src, []byte(`package main; func main() {}`), 0644); err != nil { + t.Fatal(err) + } + exe := filepath.Join(tmpdir, "a.exe") + cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags", "-H=windowsgui", "-o", exe, src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("building test executable failed: %s %s", err, out) + } + + f, err := Open(exe) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + switch oh := f.OptionalHeader.(type) { + case *OptionalHeader32: + if oh.Subsystem != IMAGE_SUBSYSTEM_WINDOWS_GUI { + t.Errorf("unexpected Subsystem value: have %d, but want %d", oh.Subsystem, IMAGE_SUBSYSTEM_WINDOWS_GUI) + } + case *OptionalHeader64: + if oh.Subsystem != IMAGE_SUBSYSTEM_WINDOWS_GUI { + t.Errorf("unexpected Subsystem value: have %d, but want %d", oh.Subsystem, IMAGE_SUBSYSTEM_WINDOWS_GUI) + } + default: + t.Fatalf("unexpected OptionalHeader type: have %T, but want *pe.OptionalHeader32 or *pe.OptionalHeader64", oh) + } +} + +func TestImportTableInUnknownSection(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("skipping Windows-only test") + } + + // ws2_32.dll import table is located in ".rdata" section, + // so it is good enough to test issue #16103. + const filename = "ws2_32.dll" + path, err := exec.LookPath(filename) + if err != nil { + t.Fatalf("unable to locate required file %q in search path: %s", filename, err) + } + + f, err := Open(path) + if err != nil { + t.Error(err) + } + defer f.Close() + + // now we can extract its imports + symbols, err := f.ImportedSymbols() + if err != nil { + t.Error(err) + } + + if len(symbols) == 0 { + t.Fatalf("unable to locate any imported symbols within file %q.", path) + } +} + +func TestInvalidOptionalHeaderMagic(t *testing.T) { + // Files with invalid optional header magic should return error from NewFile() + // (see https://golang.org/issue/30250 and https://golang.org/issue/32126 for details). + // Input generated by gofuzz + data := []byte("\x00\x00\x00\x0000000\x00\x00\x00\x00\x00\x00\x000000" + + "00000000000000000000" + + "000000000\x00\x00\x0000000000" + + "00000000000000000000" + + "0000000000000000") + + _, err := NewFile(bytes.NewReader(data)) + if err == nil { + t.Fatal("NewFile succeeded unexpectedly") + } +} + +func TestImportedSymbolsNoPanicMissingOptionalHeader(t *testing.T) { + // https://golang.org/issue/30250 + // ImportedSymbols shouldn't panic if optional headers is missing + data, err := os.ReadFile("testdata/gcc-amd64-mingw-obj") + if err != nil { + t.Fatal(err) + } + + f, err := NewFile(bytes.NewReader(data)) + if err != nil { + t.Fatal(err) + } + + if f.OptionalHeader != nil { + t.Fatal("expected f.OptionalHeader to be nil, received non-nil optional header") + } + + syms, err := f.ImportedSymbols() + if err != nil { + t.Fatal(err) + } + + if len(syms) != 0 { + t.Fatalf("expected len(syms) == 0, received len(syms) = %d", len(syms)) + } + +} + +func TestImportedSymbolsNoPanicWithSliceOutOfBound(t *testing.T) { + // https://golang.org/issue/30253 + // ImportedSymbols shouldn't panic with slice out of bounds + // Input generated by gofuzz + data := []byte("L\x01\b\x00regi\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x0f\x03" + + "\v\x01\x02\x18\x00\x0e\x00\x00\x00\x1e\x00\x00\x00\x02\x00\x00\x80\x12\x00\x00" + + "\x00\x10\x00\x00\x00 \x00\x00\x00\x00@\x00\x00\x10\x00\x00\x00\x02\x00\x00" + + "\x04\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00" + + "\x00\x04\x00\x00\x06S\x00\x00\x03\x00\x00\x00\x00\x00 \x00\x00\x10\x00\x00" + + "\x00\x00\x10\x00\x00\x10\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00`\x00\x00x\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x04\x80\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8`\x00\x00|\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00.text\x00\x00\x00d\f\x00\x00\x00\x10\x00\x00" + + "\x00\x0e\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "`\x00P`.data\x00\x00\x00\x10\x00\x00\x00\x00 \x00\x00" + + "\x00\x02\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "@\x000\xc0.rdata\x00\x004\x01\x00\x00\x000\x00\x00" + + "\x00\x02\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "@\x000@.eh_fram\xa0\x03\x00\x00\x00@\x00\x00" + + "\x00\x04\x00\x00\x00\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "@\x000@.bss\x00\x00\x00\x00`\x00\x00\x00\x00P\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x80\x000\xc0.idata\x00\x00x\x03\x00\x00\x00`\x00\x00" + + "\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00" + + "0\xc0.CRT\x00\x00\x00\x00\x18\x00\x00\x00\x00p\x00\x00\x00\x02" + + "\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00" + + "0\xc0.tls\x00\x00\x00\x00 \x00\x00\x00\x00\x80\x00\x00\x00\x02" + + "\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\xc9" + + "H\x895\x1d") + + f, err := NewFile(bytes.NewReader(data)) + if err != nil { + t.Fatal(err) + } + + syms, err := f.ImportedSymbols() + if err != nil { + t.Fatal(err) + } + + if len(syms) != 0 { + t.Fatalf("expected len(syms) == 0, received len(syms) = %d", len(syms)) + } +} diff --git a/src/debug/pe/pe.go b/src/debug/pe/pe.go new file mode 100644 index 0000000..51001bd --- /dev/null +++ b/src/debug/pe/pe.go @@ -0,0 +1,189 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pe + +type FileHeader struct { + Machine uint16 + NumberOfSections uint16 + TimeDateStamp uint32 + PointerToSymbolTable uint32 + NumberOfSymbols uint32 + SizeOfOptionalHeader uint16 + Characteristics uint16 +} + +type DataDirectory struct { + VirtualAddress uint32 + Size uint32 +} + +type OptionalHeader32 struct { + Magic uint16 + MajorLinkerVersion uint8 + MinorLinkerVersion uint8 + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + BaseOfData uint32 + ImageBase uint32 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint32 + SizeOfStackCommit uint32 + SizeOfHeapReserve uint32 + SizeOfHeapCommit uint32 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory [16]DataDirectory +} + +type OptionalHeader64 struct { + Magic uint16 + MajorLinkerVersion uint8 + MinorLinkerVersion uint8 + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory [16]DataDirectory +} + +const ( + IMAGE_FILE_MACHINE_UNKNOWN = 0x0 + IMAGE_FILE_MACHINE_AM33 = 0x1d3 + IMAGE_FILE_MACHINE_AMD64 = 0x8664 + IMAGE_FILE_MACHINE_ARM = 0x1c0 + IMAGE_FILE_MACHINE_ARMNT = 0x1c4 + IMAGE_FILE_MACHINE_ARM64 = 0xaa64 + IMAGE_FILE_MACHINE_EBC = 0xebc + IMAGE_FILE_MACHINE_I386 = 0x14c + IMAGE_FILE_MACHINE_IA64 = 0x200 + IMAGE_FILE_MACHINE_LOONGARCH32 = 0x6232 + IMAGE_FILE_MACHINE_LOONGARCH64 = 0x6264 + IMAGE_FILE_MACHINE_M32R = 0x9041 + IMAGE_FILE_MACHINE_MIPS16 = 0x266 + IMAGE_FILE_MACHINE_MIPSFPU = 0x366 + IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466 + IMAGE_FILE_MACHINE_POWERPC = 0x1f0 + IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1 + IMAGE_FILE_MACHINE_R4000 = 0x166 + IMAGE_FILE_MACHINE_SH3 = 0x1a2 + IMAGE_FILE_MACHINE_SH3DSP = 0x1a3 + IMAGE_FILE_MACHINE_SH4 = 0x1a6 + IMAGE_FILE_MACHINE_SH5 = 0x1a8 + IMAGE_FILE_MACHINE_THUMB = 0x1c2 + IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169 + IMAGE_FILE_MACHINE_RISCV32 = 0x5032 + IMAGE_FILE_MACHINE_RISCV64 = 0x5064 + IMAGE_FILE_MACHINE_RISCV128 = 0x5128 +) + +// IMAGE_DIRECTORY_ENTRY constants +const ( + IMAGE_DIRECTORY_ENTRY_EXPORT = 0 + IMAGE_DIRECTORY_ENTRY_IMPORT = 1 + IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 + IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3 + IMAGE_DIRECTORY_ENTRY_SECURITY = 4 + IMAGE_DIRECTORY_ENTRY_BASERELOC = 5 + IMAGE_DIRECTORY_ENTRY_DEBUG = 6 + IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7 + IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8 + IMAGE_DIRECTORY_ENTRY_TLS = 9 + IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10 + IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11 + IMAGE_DIRECTORY_ENTRY_IAT = 12 + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 + IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 +) + +// Values of IMAGE_FILE_HEADER.Characteristics. These can be combined together. +const ( + IMAGE_FILE_RELOCS_STRIPPED = 0x0001 + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 + IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 + IMAGE_FILE_32BIT_MACHINE = 0x0100 + IMAGE_FILE_DEBUG_STRIPPED = 0x0200 + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400 + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800 + IMAGE_FILE_SYSTEM = 0x1000 + IMAGE_FILE_DLL = 0x2000 + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000 + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 +) + +// OptionalHeader64.Subsystem and OptionalHeader32.Subsystem values. +const ( + IMAGE_SUBSYSTEM_UNKNOWN = 0 + IMAGE_SUBSYSTEM_NATIVE = 1 + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 + IMAGE_SUBSYSTEM_OS2_CUI = 5 + IMAGE_SUBSYSTEM_POSIX_CUI = 7 + IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8 + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9 + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10 + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11 + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12 + IMAGE_SUBSYSTEM_EFI_ROM = 13 + IMAGE_SUBSYSTEM_XBOX = 14 + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 +) + +// OptionalHeader64.DllCharacteristics and OptionalHeader32.DllCharacteristics +// values. These can be combined together. +const ( + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 + IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080 + IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100 + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200 + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400 + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800 + IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000 + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000 + IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000 + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 +) diff --git a/src/debug/pe/section.go b/src/debug/pe/section.go new file mode 100644 index 0000000..fabb47a --- /dev/null +++ b/src/debug/pe/section.go @@ -0,0 +1,119 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "fmt" + "internal/saferio" + "io" + "strconv" +) + +// SectionHeader32 represents real PE COFF section header. +type SectionHeader32 struct { + Name [8]uint8 + VirtualSize uint32 + VirtualAddress uint32 + SizeOfRawData uint32 + PointerToRawData uint32 + PointerToRelocations uint32 + PointerToLineNumbers uint32 + NumberOfRelocations uint16 + NumberOfLineNumbers uint16 + Characteristics uint32 +} + +// fullName finds real name of section sh. Normally name is stored +// in sh.Name, but if it is longer then 8 characters, it is stored +// in COFF string table st instead. +func (sh *SectionHeader32) fullName(st StringTable) (string, error) { + if sh.Name[0] != '/' { + return cstring(sh.Name[:]), nil + } + i, err := strconv.Atoi(cstring(sh.Name[1:])) + if err != nil { + return "", err + } + return st.String(uint32(i)) +} + +// TODO(brainman): copy all IMAGE_REL_* consts from ldpe.go here + +// Reloc represents a PE COFF relocation. +// Each section contains its own relocation list. +type Reloc struct { + VirtualAddress uint32 + SymbolTableIndex uint32 + Type uint16 +} + +func readRelocs(sh *SectionHeader, r io.ReadSeeker) ([]Reloc, error) { + if sh.NumberOfRelocations <= 0 { + return nil, nil + } + _, err := r.Seek(int64(sh.PointerToRelocations), seekStart) + if err != nil { + return nil, fmt.Errorf("fail to seek to %q section relocations: %v", sh.Name, err) + } + relocs := make([]Reloc, sh.NumberOfRelocations) + err = binary.Read(r, binary.LittleEndian, relocs) + if err != nil { + return nil, fmt.Errorf("fail to read section relocations: %v", err) + } + return relocs, nil +} + +// SectionHeader is similar to SectionHeader32 with Name +// field replaced by Go string. +type SectionHeader struct { + Name string + VirtualSize uint32 + VirtualAddress uint32 + Size uint32 + Offset uint32 + PointerToRelocations uint32 + PointerToLineNumbers uint32 + NumberOfRelocations uint16 + NumberOfLineNumbers uint16 + Characteristics uint32 +} + +// Section provides access to PE COFF section. +type Section struct { + SectionHeader + Relocs []Reloc + + // Embed ReaderAt for ReadAt method. + // Do not embed SectionReader directly + // to avoid having Read and Seek. + // If a client wants Read and Seek it must use + // Open() to avoid fighting over the seek offset + // with other clients. + io.ReaderAt + sr *io.SectionReader +} + +// Data reads and returns the contents of the PE section s. +func (s *Section) Data() ([]byte, error) { + return saferio.ReadDataAt(s.sr, uint64(s.Size), 0) +} + +// Open returns a new ReadSeeker reading the PE section s. +func (s *Section) Open() io.ReadSeeker { + return io.NewSectionReader(s.sr, 0, 1<<63-1) +} + +// Section characteristics flags. +const ( + IMAGE_SCN_CNT_CODE = 0x00000020 + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 + IMAGE_SCN_LNK_COMDAT = 0x00001000 + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 + IMAGE_SCN_MEM_EXECUTE = 0x20000000 + IMAGE_SCN_MEM_READ = 0x40000000 + IMAGE_SCN_MEM_WRITE = 0x80000000 +) diff --git a/src/debug/pe/string.go b/src/debug/pe/string.go new file mode 100644 index 0000000..a156bbe --- /dev/null +++ b/src/debug/pe/string.go @@ -0,0 +1,69 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pe + +import ( + "bytes" + "encoding/binary" + "fmt" + "internal/saferio" + "io" +) + +// cstring converts ASCII byte sequence b to string. +// It stops once it finds 0 or reaches end of b. +func cstring(b []byte) string { + i := bytes.IndexByte(b, 0) + if i == -1 { + i = len(b) + } + return string(b[:i]) +} + +// StringTable is a COFF string table. +type StringTable []byte + +func readStringTable(fh *FileHeader, r io.ReadSeeker) (StringTable, error) { + // COFF string table is located right after COFF symbol table. + if fh.PointerToSymbolTable <= 0 { + return nil, nil + } + offset := fh.PointerToSymbolTable + COFFSymbolSize*fh.NumberOfSymbols + _, err := r.Seek(int64(offset), seekStart) + if err != nil { + return nil, fmt.Errorf("fail to seek to string table: %v", err) + } + var l uint32 + err = binary.Read(r, binary.LittleEndian, &l) + if err != nil { + return nil, fmt.Errorf("fail to read string table length: %v", err) + } + // string table length includes itself + if l <= 4 { + return nil, nil + } + l -= 4 + + buf, err := saferio.ReadData(r, uint64(l)) + if err != nil { + return nil, fmt.Errorf("fail to read string table: %v", err) + } + return StringTable(buf), nil +} + +// TODO(brainman): decide if start parameter should be int instead of uint32 + +// String extracts string from COFF string table st at offset start. +func (st StringTable) String(start uint32) (string, error) { + // start includes 4 bytes of string table length + if start < 4 { + return "", fmt.Errorf("offset %d is before the start of string table", start) + } + start -= 4 + if int(start) > len(st) { + return "", fmt.Errorf("offset %d is beyond the end of string table", start) + } + return cstring(st[start:]), nil +} diff --git a/src/debug/pe/symbol.go b/src/debug/pe/symbol.go new file mode 100644 index 0000000..b1654f8 --- /dev/null +++ b/src/debug/pe/symbol.go @@ -0,0 +1,209 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "errors" + "fmt" + "internal/saferio" + "io" + "unsafe" +) + +const COFFSymbolSize = 18 + +// COFFSymbol represents single COFF symbol table record. +type COFFSymbol struct { + Name [8]uint8 + Value uint32 + SectionNumber int16 + Type uint16 + StorageClass uint8 + NumberOfAuxSymbols uint8 +} + +// readCOFFSymbols reads in the symbol table for a PE file, returning +// a slice of COFFSymbol objects. The PE format includes both primary +// symbols (whose fields are described by COFFSymbol above) and +// auxiliary symbols; all symbols are 18 bytes in size. The auxiliary +// symbols for a given primary symbol are placed following it in the +// array, e.g. +// +// ... +// k+0: regular sym k +// k+1: 1st aux symbol for k +// k+2: 2nd aux symbol for k +// k+3: regular sym k+3 +// k+4: 1st aux symbol for k+3 +// k+5: regular sym k+5 +// k+6: regular sym k+6 +// +// The PE format allows for several possible aux symbol formats. For +// more info see: +// +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-symbol-records +// +// At the moment this package only provides APIs for looking at +// aux symbols of format 5 (associated with section definition symbols). +func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) { + if fh.PointerToSymbolTable == 0 { + return nil, nil + } + if fh.NumberOfSymbols <= 0 { + return nil, nil + } + _, err := r.Seek(int64(fh.PointerToSymbolTable), seekStart) + if err != nil { + return nil, fmt.Errorf("fail to seek to symbol table: %v", err) + } + c := saferio.SliceCap((*COFFSymbol)(nil), uint64(fh.NumberOfSymbols)) + if c < 0 { + return nil, errors.New("too many symbols; file may be corrupt") + } + syms := make([]COFFSymbol, 0, c) + naux := 0 + for k := uint32(0); k < fh.NumberOfSymbols; k++ { + var sym COFFSymbol + if naux == 0 { + // Read a primary symbol. + err = binary.Read(r, binary.LittleEndian, &sym) + if err != nil { + return nil, fmt.Errorf("fail to read symbol table: %v", err) + } + // Record how many auxiliary symbols it has. + naux = int(sym.NumberOfAuxSymbols) + } else { + // Read an aux symbol. At the moment we assume all + // aux symbols are format 5 (obviously this doesn't always + // hold; more cases will be needed below if more aux formats + // are supported in the future). + naux-- + aux := (*COFFSymbolAuxFormat5)(unsafe.Pointer(&sym)) + err = binary.Read(r, binary.LittleEndian, aux) + if err != nil { + return nil, fmt.Errorf("fail to read symbol table: %v", err) + } + } + syms = append(syms, sym) + } + if naux != 0 { + return nil, fmt.Errorf("fail to read symbol table: %d aux symbols unread", naux) + } + return syms, nil +} + +// isSymNameOffset checks symbol name if it is encoded as offset into string table. +func isSymNameOffset(name [8]byte) (bool, uint32) { + if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 { + return true, binary.LittleEndian.Uint32(name[4:]) + } + return false, 0 +} + +// FullName finds real name of symbol sym. Normally name is stored +// in sym.Name, but if it is longer then 8 characters, it is stored +// in COFF string table st instead. +func (sym *COFFSymbol) FullName(st StringTable) (string, error) { + if ok, offset := isSymNameOffset(sym.Name); ok { + return st.String(offset) + } + return cstring(sym.Name[:]), nil +} + +func removeAuxSymbols(allsyms []COFFSymbol, st StringTable) ([]*Symbol, error) { + if len(allsyms) == 0 { + return nil, nil + } + syms := make([]*Symbol, 0) + aux := uint8(0) + for _, sym := range allsyms { + if aux > 0 { + aux-- + continue + } + name, err := sym.FullName(st) + if err != nil { + return nil, err + } + aux = sym.NumberOfAuxSymbols + s := &Symbol{ + Name: name, + Value: sym.Value, + SectionNumber: sym.SectionNumber, + Type: sym.Type, + StorageClass: sym.StorageClass, + } + syms = append(syms, s) + } + return syms, nil +} + +// Symbol is similar to COFFSymbol with Name field replaced +// by Go string. Symbol also does not have NumberOfAuxSymbols. +type Symbol struct { + Name string + Value uint32 + SectionNumber int16 + Type uint16 + StorageClass uint8 +} + +// COFFSymbolAuxFormat5 describes the expected form of an aux symbol +// attached to a section definition symbol. The PE format defines a +// number of different aux symbol formats: format 1 for function +// definitions, format 2 for .be and .ef symbols, and so on. Format 5 +// holds extra info associated with a section definition, including +// number of relocations + line numbers, as well as COMDAT info. See +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-format-5-section-definitions +// for more on what's going on here. +type COFFSymbolAuxFormat5 struct { + Size uint32 + NumRelocs uint16 + NumLineNumbers uint16 + Checksum uint32 + SecNum uint16 + Selection uint8 + _ [3]uint8 // padding +} + +// These constants make up the possible values for the 'Selection' +// field in an AuxFormat5. +const ( + IMAGE_COMDAT_SELECT_NODUPLICATES = 1 + IMAGE_COMDAT_SELECT_ANY = 2 + IMAGE_COMDAT_SELECT_SAME_SIZE = 3 + IMAGE_COMDAT_SELECT_EXACT_MATCH = 4 + IMAGE_COMDAT_SELECT_ASSOCIATIVE = 5 + IMAGE_COMDAT_SELECT_LARGEST = 6 +) + +// COFFSymbolReadSectionDefAux returns a blob of axiliary information +// (including COMDAT info) for a section definition symbol. Here 'idx' +// is the index of a section symbol in the main COFFSymbol array for +// the File. Return value is a pointer to the appropriate aux symbol +// struct. For more info, see: +// +// auxiliary symbols: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-symbol-records +// COMDAT sections: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#comdat-sections-object-only +// auxiliary info for section definitions: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#auxiliary-format-5-section-definitions +func (f *File) COFFSymbolReadSectionDefAux(idx int) (*COFFSymbolAuxFormat5, error) { + var rv *COFFSymbolAuxFormat5 + if idx < 0 || idx >= len(f.COFFSymbols) { + return rv, fmt.Errorf("invalid symbol index") + } + pesym := &f.COFFSymbols[idx] + const IMAGE_SYM_CLASS_STATIC = 3 + if pesym.StorageClass != uint8(IMAGE_SYM_CLASS_STATIC) { + return rv, fmt.Errorf("incorrect symbol storage class") + } + if pesym.NumberOfAuxSymbols == 0 || idx+1 >= len(f.COFFSymbols) { + return rv, fmt.Errorf("aux symbol unavailable") + } + // Locate and return a pointer to the successor aux symbol. + pesymn := &f.COFFSymbols[idx+1] + rv = (*COFFSymbolAuxFormat5)(unsafe.Pointer(pesymn)) + return rv, nil +} diff --git a/src/debug/pe/symbols_test.go b/src/debug/pe/symbols_test.go new file mode 100644 index 0000000..c4dcd95 --- /dev/null +++ b/src/debug/pe/symbols_test.go @@ -0,0 +1,91 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pe + +import ( + "fmt" + "testing" +) + +type testpoint struct { + name string + ok bool + err string + auxstr string +} + +func TestReadCOFFSymbolAuxInfo(t *testing.T) { + testpoints := map[int]testpoint{ + 39: testpoint{ + name: ".rdata$.refptr.__native_startup_lock", + ok: true, + auxstr: "{Size:8 NumRelocs:1 NumLineNumbers:0 Checksum:0 SecNum:16 Selection:2 _:[0 0 0]}", + }, + 81: testpoint{ + name: ".debug_line", + ok: true, + auxstr: "{Size:994 NumRelocs:1 NumLineNumbers:0 Checksum:1624223678 SecNum:32 Selection:0 _:[0 0 0]}", + }, + 155: testpoint{ + name: ".file", + ok: false, + err: "incorrect symbol storage class", + }, + } + + // The testdata PE object file below was selected from a release + // build from https://github.com/mstorsjo/llvm-mingw/releases; it + // corresponds to the mingw "crt2.o" object. The object itself was + // built using an x86_64 HOST=linux TARGET=windows clang cross + // compiler based on LLVM 13. More build details can be found at + // https://github.com/mstorsjo/llvm-mingw/releases. + f, err := Open("testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2") + if err != nil { + t.Errorf("open failed with %v", err) + } + defer f.Close() + for k := range f.COFFSymbols { + tp, ok := testpoints[k] + if !ok { + continue + } + sym := &f.COFFSymbols[k] + if sym.NumberOfAuxSymbols == 0 { + t.Errorf("expected aux symbols for sym %d", k) + continue + } + name, nerr := sym.FullName(f.StringTable) + if nerr != nil { + t.Errorf("FullName(%d) failed with %v", k, nerr) + continue + } + if name != tp.name { + t.Errorf("name check for %d, got %s want %s", k, name, tp.name) + continue + } + ap, err := f.COFFSymbolReadSectionDefAux(k) + if tp.ok { + if err != nil { + t.Errorf("unexpected failure on %d, got error %v", k, err) + continue + } + got := fmt.Sprintf("%+v", *ap) + if got != tp.auxstr { + t.Errorf("COFFSymbolReadSectionDefAux on %d bad return, got:\n%s\nwant:\n%s\n", k, got, tp.auxstr) + continue + } + } else { + if err == nil { + t.Errorf("unexpected non-failure on %d", k) + continue + } + got := fmt.Sprintf("%v", err) + if got != tp.err { + t.Errorf("COFFSymbolReadSectionDefAux %d wrong error, got %q want %q", k, got, tp.err) + continue + } + } + } +} diff --git a/src/debug/pe/testdata/gcc-386-mingw-exec b/src/debug/pe/testdata/gcc-386-mingw-exec Binary files differnew file mode 100644 index 0000000..4b808d0 --- /dev/null +++ b/src/debug/pe/testdata/gcc-386-mingw-exec diff --git a/src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec b/src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec Binary files differnew file mode 100644 index 0000000..329dca6 --- /dev/null +++ b/src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec diff --git a/src/debug/pe/testdata/gcc-386-mingw-obj b/src/debug/pe/testdata/gcc-386-mingw-obj Binary files differnew file mode 100644 index 0000000..0c84d89 --- /dev/null +++ b/src/debug/pe/testdata/gcc-386-mingw-obj diff --git a/src/debug/pe/testdata/gcc-amd64-mingw-exec b/src/debug/pe/testdata/gcc-amd64-mingw-exec Binary files differnew file mode 100644 index 0000000..ce6feb6 --- /dev/null +++ b/src/debug/pe/testdata/gcc-amd64-mingw-exec diff --git a/src/debug/pe/testdata/gcc-amd64-mingw-obj b/src/debug/pe/testdata/gcc-amd64-mingw-obj Binary files differnew file mode 100644 index 0000000..48ae792 --- /dev/null +++ b/src/debug/pe/testdata/gcc-amd64-mingw-obj diff --git a/src/debug/pe/testdata/hello.c b/src/debug/pe/testdata/hello.c new file mode 100644 index 0000000..a689d36 --- /dev/null +++ b/src/debug/pe/testdata/hello.c @@ -0,0 +1,8 @@ +#include <stdio.h> + +int +main(void) +{ + printf("hello, world\n"); + return 0; +} diff --git a/src/debug/pe/testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2 b/src/debug/pe/testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2 Binary files differnew file mode 100644 index 0000000..5576c1c --- /dev/null +++ b/src/debug/pe/testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2 diff --git a/src/debug/pe/testdata/vmlinuz-4.15.0-47-generic b/src/debug/pe/testdata/vmlinuz-4.15.0-47-generic Binary files differnew file mode 100644 index 0000000..d01cf61 --- /dev/null +++ b/src/debug/pe/testdata/vmlinuz-4.15.0-47-generic |