summaryrefslogtreecommitdiffstats
path: root/src/debug/pe
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/pe')
-rw-r--r--src/debug/pe/file.go630
-rw-r--r--src/debug/pe/file_cgo_test.go35
-rw-r--r--src/debug/pe/file_test.go745
-rw-r--r--src/debug/pe/pe.go189
-rw-r--r--src/debug/pe/section.go119
-rw-r--r--src/debug/pe/string.go69
-rw-r--r--src/debug/pe/symbol.go209
-rw-r--r--src/debug/pe/symbols_test.go91
-rw-r--r--src/debug/pe/testdata/gcc-386-mingw-execbin0 -> 29941 bytes
-rw-r--r--src/debug/pe/testdata/gcc-386-mingw-no-symbols-execbin0 -> 8704 bytes
-rw-r--r--src/debug/pe/testdata/gcc-386-mingw-objbin0 -> 2372 bytes
-rw-r--r--src/debug/pe/testdata/gcc-amd64-mingw-execbin0 -> 273083 bytes
-rw-r--r--src/debug/pe/testdata/gcc-amd64-mingw-objbin0 -> 736 bytes
-rw-r--r--src/debug/pe/testdata/hello.c8
-rw-r--r--src/debug/pe/testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2bin0 -> 24046 bytes
-rw-r--r--src/debug/pe/testdata/vmlinuz-4.15.0-47-genericbin0 -> 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
new file mode 100644
index 0000000..4b808d0
--- /dev/null
+++ b/src/debug/pe/testdata/gcc-386-mingw-exec
Binary files differ
diff --git a/src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec b/src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec
new file mode 100644
index 0000000..329dca6
--- /dev/null
+++ b/src/debug/pe/testdata/gcc-386-mingw-no-symbols-exec
Binary files differ
diff --git a/src/debug/pe/testdata/gcc-386-mingw-obj b/src/debug/pe/testdata/gcc-386-mingw-obj
new file mode 100644
index 0000000..0c84d89
--- /dev/null
+++ b/src/debug/pe/testdata/gcc-386-mingw-obj
Binary files differ
diff --git a/src/debug/pe/testdata/gcc-amd64-mingw-exec b/src/debug/pe/testdata/gcc-amd64-mingw-exec
new file mode 100644
index 0000000..ce6feb6
--- /dev/null
+++ b/src/debug/pe/testdata/gcc-amd64-mingw-exec
Binary files differ
diff --git a/src/debug/pe/testdata/gcc-amd64-mingw-obj b/src/debug/pe/testdata/gcc-amd64-mingw-obj
new file mode 100644
index 0000000..48ae792
--- /dev/null
+++ b/src/debug/pe/testdata/gcc-amd64-mingw-obj
Binary files differ
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
new file mode 100644
index 0000000..5576c1c
--- /dev/null
+++ b/src/debug/pe/testdata/llvm-mingw-20211002-msvcrt-x86_64-crt2
Binary files differ
diff --git a/src/debug/pe/testdata/vmlinuz-4.15.0-47-generic b/src/debug/pe/testdata/vmlinuz-4.15.0-47-generic
new file mode 100644
index 0000000..d01cf61
--- /dev/null
+++ b/src/debug/pe/testdata/vmlinuz-4.15.0-47-generic
Binary files differ