// Copyright 2013 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. // Parsing of PE executables (Microsoft Windows). package objfile import ( "debug/dwarf" "debug/pe" "fmt" "io" "slices" "sort" ) type peFile struct { pe *pe.File } func openPE(r io.ReaderAt) (rawFile, error) { f, err := pe.NewFile(r) if err != nil { return nil, err } return &peFile{f}, nil } func (f *peFile) symbols() ([]Sym, error) { // Build sorted list of addresses of all symbols. // We infer the size of a symbol by looking at where the next symbol begins. var addrs []uint64 imageBase, _ := f.imageBase() var syms []Sym for _, s := range f.pe.Symbols { const ( N_UNDEF = 0 // An undefined (extern) symbol N_ABS = -1 // An absolute symbol (e_value is a constant, not an address) N_DEBUG = -2 // A debugging symbol ) sym := Sym{Name: s.Name, Addr: uint64(s.Value), Code: '?'} switch s.SectionNumber { case N_UNDEF: sym.Code = 'U' case N_ABS: sym.Code = 'C' case N_DEBUG: sym.Code = '?' default: if s.SectionNumber < 0 || len(f.pe.Sections) < int(s.SectionNumber) { return nil, fmt.Errorf("invalid section number in symbol table") } sect := f.pe.Sections[s.SectionNumber-1] const ( text = 0x20 data = 0x40 bss = 0x80 permW = 0x80000000 ) ch := sect.Characteristics switch { case ch&text != 0: sym.Code = 'T' case ch&data != 0: if ch&permW == 0 { sym.Code = 'R' } else { sym.Code = 'D' } case ch&bss != 0: sym.Code = 'B' } sym.Addr += imageBase + uint64(sect.VirtualAddress) } syms = append(syms, sym) addrs = append(addrs, sym.Addr) } slices.Sort(addrs) for i := range syms { j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr }) if j < len(addrs) { syms[i].Size = int64(addrs[j] - syms[i].Addr) } } return syms, nil } func (f *peFile) pcln() (textStart uint64, symtab, pclntab []byte, err error) { imageBase, err := f.imageBase() if err != nil { return 0, nil, nil, err } if sect := f.pe.Section(".text"); sect != nil { textStart = imageBase + uint64(sect.VirtualAddress) } if pclntab, err = loadPETable(f.pe, "runtime.pclntab", "runtime.epclntab"); err != nil { // We didn't find the symbols, so look for the names used in 1.3 and earlier. // TODO: Remove code looking for the old symbols when we no longer care about 1.3. var err2 error if pclntab, err2 = loadPETable(f.pe, "pclntab", "epclntab"); err2 != nil { return 0, nil, nil, err } } if symtab, err = loadPETable(f.pe, "runtime.symtab", "runtime.esymtab"); err != nil { // Same as above. var err2 error if symtab, err2 = loadPETable(f.pe, "symtab", "esymtab"); err2 != nil { return 0, nil, nil, err } } return textStart, symtab, pclntab, nil } func (f *peFile) text() (textStart uint64, text []byte, err error) { imageBase, err := f.imageBase() if err != nil { return 0, nil, err } sect := f.pe.Section(".text") if sect == nil { return 0, nil, fmt.Errorf("text section not found") } textStart = imageBase + uint64(sect.VirtualAddress) text, err = sect.Data() return } func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { for _, s := range f.Symbols { if s.Name != name { continue } if s.SectionNumber <= 0 { return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber) } if len(f.Sections) < int(s.SectionNumber) { return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections)) } return s, nil } return nil, fmt.Errorf("no %s symbol found", name) } func loadPETable(f *pe.File, sname, ename string) ([]byte, error) { ssym, err := findPESymbol(f, sname) if err != nil { return nil, err } esym, err := findPESymbol(f, ename) if err != nil { return nil, err } if ssym.SectionNumber != esym.SectionNumber { return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename) } sect := f.Sections[ssym.SectionNumber-1] data, err := sect.Data() if err != nil { return nil, err } return data[ssym.Value:esym.Value], nil } func (f *peFile) goarch() string { switch f.pe.Machine { case pe.IMAGE_FILE_MACHINE_I386: return "386" case pe.IMAGE_FILE_MACHINE_AMD64: return "amd64" case pe.IMAGE_FILE_MACHINE_ARMNT: return "arm" case pe.IMAGE_FILE_MACHINE_ARM64: return "arm64" default: return "" } } func (f *peFile) loadAddress() (uint64, error) { return f.imageBase() } func (f *peFile) imageBase() (uint64, error) { switch oh := f.pe.OptionalHeader.(type) { case *pe.OptionalHeader32: return uint64(oh.ImageBase), nil case *pe.OptionalHeader64: return oh.ImageBase, nil default: return 0, fmt.Errorf("pe file format not recognized") } } func (f *peFile) dwarf() (*dwarf.Data, error) { return f.pe.DWARF() }