cmd/link/internal/ld: use debug/pe package to rewrite ldpe.go

This CL also includes vendored copy of debug/pe,
otherwise bootstrapping fails.

Updates #15345

Change-Id: I3a8ac990e3cb12cb4d24ec11b618b68190397fd1
Reviewed-on: https://go-review.googlesource.com/22603
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Alex Brainman 2016-04-29 16:23:55 +10:00 committed by Russ Cox
parent 2427123d93
commit d08c3d1329
16 changed files with 1327 additions and 223 deletions

View File

@ -36,6 +36,11 @@ go src=..
gofmt_test.go
testdata
+
link
internal
pe
testdata
+
vendor
golang.org
x

View File

@ -55,6 +55,7 @@ var bootstrapDirs = []string{
"link/internal/amd64",
"link/internal/arm",
"link/internal/arm64",
"link/internal/pe",
"link/internal/ld",
"link/internal/mips64",
"link/internal/ppc64",

View File

@ -8,12 +8,12 @@ import (
"cmd/internal/bio"
"cmd/internal/obj"
"cmd/internal/sys"
"encoding/binary"
"cmd/link/internal/pe"
"errors"
"fmt"
"io"
"log"
"sort"
"strconv"
"strings"
)
@ -100,155 +100,75 @@ const (
IMAGE_REL_AMD64_SSPAN32 = 0x0010
)
type PeSym struct {
name string
value uint32
sectnum uint16
type_ uint16
sclass uint8
aux uint8
sym *Symbol
// TODO(brainman): maybe just add ReadAt method to bio.Reader instead of creating peBiobuf
// peBiobuf makes bio.Reader look like io.ReaderAt.
type peBiobuf bio.Reader
func (f *peBiobuf) ReadAt(p []byte, off int64) (int, error) {
ret := ((*bio.Reader)(f)).Seek(off, 0)
if ret < 0 {
return 0, errors.New("fail to seek")
}
n, err := f.Read(p)
if err != nil {
return 0, err
}
return n, nil
}
type PeSect struct {
name string
base []byte
size uint64
sym *Symbol
sh IMAGE_SECTION_HEADER
}
// TODO(brainman): remove 'goto bad' everywhere inside ldpe
type PeObj struct {
f *bio.Reader
name string
base uint32
sect []PeSect
nsect uint
pesym []PeSym
npesym uint
fh IMAGE_FILE_HEADER
snames []byte
}
func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
func ldpe(ctxt *Link, input *bio.Reader, pkg string, length int64, pn string) {
if ctxt.Debugvlog != 0 {
ctxt.Logf("%5.2f ldpe %s\n", obj.Cputime(), pn)
}
var sect *PeSect
localSymVersion := ctxt.Syms.IncVersion()
base := f.Offset()
peobj := new(PeObj)
peobj.f = f
peobj.base = uint32(base)
peobj.name = pn
sectsyms := make(map[*pe.Section]*Symbol)
sectdata := make(map[*pe.Section][]byte)
// read header
var err error
var j int
var l uint32
var name string
var numaux int
var r []Reloc
var rp *Reloc
var rsect *PeSect
var s *Symbol
var sym *PeSym
var symbuf [18]uint8
if err = binary.Read(f, binary.LittleEndian, &peobj.fh); err != nil {
// Some input files are archives containing multiple of
// object files, and pe.NewFile seeks to the start of
// input file and get confused. Create section reader
// to stop pe.NewFile looking before current position.
sr := io.NewSectionReader((*peBiobuf)(input), input.Offset(), 1<<63-1)
// TODO: replace pe.NewFile with pe.Load (grep for "add Load function" in debug/pe for details)
f, err := pe.NewFile(sr)
if err != nil {
goto bad
}
// load section list
peobj.sect = make([]PeSect, peobj.fh.NumberOfSections)
peobj.nsect = uint(peobj.fh.NumberOfSections)
for i := 0; i < int(peobj.fh.NumberOfSections); i++ {
if err = binary.Read(f, binary.LittleEndian, &peobj.sect[i].sh); err != nil {
goto bad
}
peobj.sect[i].size = uint64(peobj.sect[i].sh.SizeOfRawData)
peobj.sect[i].name = cstring(peobj.sect[i].sh.Name[:])
}
defer f.Close()
// TODO return error if found .cormeta
// load string table
f.Seek(base+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0)
if _, err := io.ReadFull(f, symbuf[:4]); err != nil {
goto bad
}
l = Le32(symbuf[:])
peobj.snames = make([]byte, l)
f.Seek(base+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0)
if _, err := io.ReadFull(f, peobj.snames); err != nil {
goto bad
}
// rewrite section names if they start with /
for i := 0; i < int(peobj.fh.NumberOfSections); i++ {
if peobj.sect[i].name == "" {
continue
}
if peobj.sect[i].name[0] != '/' {
continue
}
n, _ := strconv.Atoi(peobj.sect[i].name[1:])
peobj.sect[i].name = cstring(peobj.snames[n:])
}
// read symbols
peobj.pesym = make([]PeSym, peobj.fh.NumberOfSymbols)
peobj.npesym = uint(peobj.fh.NumberOfSymbols)
f.Seek(base+int64(peobj.fh.PointerToSymbolTable), 0)
for i := 0; uint32(i) < peobj.fh.NumberOfSymbols; i += numaux + 1 {
f.Seek(base+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(i), 0)
if _, err := io.ReadFull(f, symbuf[:]); err != nil {
goto bad
}
if (symbuf[0] == 0) && (symbuf[1] == 0) && (symbuf[2] == 0) && (symbuf[3] == 0) {
l = Le32(symbuf[4:])
peobj.pesym[i].name = cstring(peobj.snames[l:]) // sym name length <= 8
} else {
peobj.pesym[i].name = cstring(symbuf[:8])
}
peobj.pesym[i].value = Le32(symbuf[8:])
peobj.pesym[i].sectnum = Le16(symbuf[12:])
peobj.pesym[i].sclass = symbuf[16]
peobj.pesym[i].aux = symbuf[17]
peobj.pesym[i].type_ = Le16(symbuf[14:])
numaux = int(peobj.pesym[i].aux)
if numaux < 0 {
numaux = 0
}
}
// create symbols for mapped sections
for i := 0; uint(i) < peobj.nsect; i++ {
sect = &peobj.sect[i]
if sect.sh.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
for _, sect := range f.Sections {
if sect.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
continue
}
if sect.sh.Characteristics&(IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
if sect.Characteristics&(IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
// This has been seen for .idata sections, which we
// want to ignore. See issues 5106 and 5273.
continue
}
if pemap(peobj, sect) < 0 {
data, err2 := sect.Data()
if err2 != nil {
err = err2
goto bad
}
sectdata[sect] = data
name = fmt.Sprintf("%s(%s)", pkg, sect.name)
s = ctxt.Syms.Lookup(name, localSymVersion)
name := fmt.Sprintf("%s(%s)", pkg, sect.Name)
s := ctxt.Syms.Lookup(name, localSymVersion)
switch sect.sh.Characteristics & (IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE) {
switch sect.Characteristics & (IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE) {
case IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ: //.rdata
s.Type = obj.SRODATA
@ -262,58 +182,63 @@ func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
s.Type = obj.STEXT
default:
err = fmt.Errorf("unexpected flags %#06x for PE section %s", sect.sh.Characteristics, sect.name)
err = fmt.Errorf("unexpected flags %#06x for PE section %s", sect.Characteristics, sect.Name)
goto bad
}
s.P = sect.base
s.P = s.P[:sect.size]
s.Size = int64(sect.size)
sect.sym = s
if sect.name == ".rsrc" {
setpersrc(ctxt, sect.sym)
s.P = data
s.Size = int64(len(data))
sectsyms[sect] = s
if sect.Name == ".rsrc" {
setpersrc(ctxt, s)
}
}
// load relocations
for i := 0; uint(i) < peobj.nsect; i++ {
rsect = &peobj.sect[i]
if rsect.sym == nil || rsect.sh.NumberOfRelocations == 0 {
for _, rsect := range f.Sections {
if _, found := sectsyms[rsect]; !found {
continue
}
if rsect.sh.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
if rsect.NumberOfRelocations == 0 {
continue
}
if sect.sh.Characteristics&(IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
if rsect.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
continue
}
if rsect.Characteristics&(IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
// This has been seen for .idata sections, which we
// want to ignore. See issues 5106 and 5273.
continue
}
r = make([]Reloc, rsect.sh.NumberOfRelocations)
f.Seek(int64(peobj.base)+int64(rsect.sh.PointerToRelocations), 0)
for j = 0; j < int(rsect.sh.NumberOfRelocations); j++ {
rp = &r[j]
if _, err := io.ReadFull(f, symbuf[:10]); err != nil {
rs := make([]Reloc, rsect.NumberOfRelocations)
for j, r := range rsect.Relocs {
rp := &rs[j]
if int(r.SymbolTableIndex) >= len(f.COFFSymbols) {
err = fmt.Errorf("relocation number %d symbol index idx=%d cannot be large then number of symbols %d", j, r.SymbolTableIndex, len(f.COFFSymbols))
goto bad
}
rva := Le32(symbuf[0:])
symindex := Le32(symbuf[4:])
type_ := Le16(symbuf[8:])
if err = readpesym(ctxt, peobj, int(symindex), &sym, localSymVersion); err != nil {
pesym := &f.COFFSymbols[r.SymbolTableIndex]
gosym, err2 := readpesym(ctxt, f, pesym, sectsyms, localSymVersion)
if err2 != nil {
err = err2
goto bad
}
if sym.sym == nil {
err = fmt.Errorf("reloc of invalid sym %s idx=%d type=%d", sym.name, symindex, sym.type_)
if gosym == nil {
name, err2 := pesym.FullName(f.StringTable)
if err2 != nil {
name = string(pesym.Name[:])
}
err = fmt.Errorf("reloc of invalid sym %s idx=%d type=%d", name, r.SymbolTableIndex, pesym.Type)
goto bad
}
rp.Sym = sym.sym
rp.Sym = gosym
rp.Siz = 4
rp.Off = int32(rva)
switch type_ {
rp.Off = int32(r.VirtualAddress)
switch r.Type {
default:
Errorf(rsect.sym, "%s: unknown relocation type %d;", pn, type_)
Errorf(sectsyms[rsect], "%s: unknown relocation type %d;", pn, r.Type)
fallthrough
case IMAGE_REL_I386_REL32, IMAGE_REL_AMD64_REL32,
@ -321,13 +246,13 @@ func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
IMAGE_REL_AMD64_ADDR32NB:
rp.Type = obj.R_PCREL
rp.Add = int64(int32(Le32(rsect.base[rp.Off:])))
rp.Add = int64(int32(Le32(sectdata[rsect][rp.Off:])))
case IMAGE_REL_I386_DIR32NB, IMAGE_REL_I386_DIR32:
rp.Type = obj.R_ADDR
// load addend from image
rp.Add = int64(int32(Le32(rsect.base[rp.Off:])))
rp.Add = int64(int32(Le32(sectdata[rsect][rp.Off:])))
case IMAGE_REL_AMD64_ADDR64: // R_X86_64_64
rp.Siz = 8
@ -335,60 +260,74 @@ func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
rp.Type = obj.R_ADDR
// load addend from image
rp.Add = int64(Le64(rsect.base[rp.Off:]))
rp.Add = int64(Le64(sectdata[rsect][rp.Off:]))
}
// ld -r could generate multiple section symbols for the
// same section but with different values, we have to take
// that into account
if issect(&peobj.pesym[symindex]) {
rp.Add += int64(peobj.pesym[symindex].value)
if issect(pesym) {
rp.Add += int64(pesym.Value)
}
}
sort.Sort(rbyoff(r[:rsect.sh.NumberOfRelocations]))
sort.Sort(rbyoff(rs[:rsect.NumberOfRelocations]))
s = rsect.sym
s.R = r
s.R = s.R[:rsect.sh.NumberOfRelocations]
s := sectsyms[rsect]
s.R = rs
s.R = s.R[:rsect.NumberOfRelocations]
}
// enter sub-symbols into symbol table.
for i := 0; uint(i) < peobj.npesym; i++ {
if peobj.pesym[i].name == "" {
for i, numaux := 0, 0; i < len(f.COFFSymbols); i += numaux + 1 {
pesym := &f.COFFSymbols[i]
numaux = int(pesym.NumberOfAuxSymbols)
name, err2 := pesym.FullName(f.StringTable)
if err2 != nil {
err = err2
goto bad
}
if name == "" {
continue
}
if issect(&peobj.pesym[i]) {
if issect(pesym) {
continue
}
if uint(peobj.pesym[i].sectnum) > peobj.nsect {
if int(pesym.SectionNumber) > len(f.Sections) {
continue
}
if peobj.pesym[i].sectnum > 0 {
sect = &peobj.sect[peobj.pesym[i].sectnum-1]
if sect.sym == nil {
if pesym.SectionNumber == IMAGE_SYM_DEBUG {
continue
}
var sect *pe.Section
if pesym.SectionNumber > 0 {
sect = f.Sections[pesym.SectionNumber-1]
if _, found := sectsyms[sect]; !found {
continue
}
}
if err = readpesym(ctxt, peobj, i, &sym, localSymVersion); err != nil {
s, err2 := readpesym(ctxt, f, pesym, sectsyms, localSymVersion)
if err2 != nil {
err = err2
goto bad
}
s = sym.sym
if sym.sectnum == 0 { // extern
if pesym.SectionNumber == 0 { // extern
if s.Type == obj.SDYNIMPORT {
s.Plt = -2 // flag for dynimport in PE object files.
}
if s.Type == obj.SXREF && sym.value > 0 { // global data
if s.Type == obj.SXREF && pesym.Value > 0 { // global data
s.Type = obj.SNOPTRDATA
s.Size = int64(sym.value)
s.Size = int64(pesym.Value)
}
continue
} else if sym.sectnum > 0 && uint(sym.sectnum) <= peobj.nsect {
sect = &peobj.sect[sym.sectnum-1]
if sect.sym == nil {
} else if pesym.SectionNumber > 0 && int(pesym.SectionNumber) <= len(f.Sections) {
sect = f.Sections[pesym.SectionNumber-1]
if _, found := sectsyms[sect]; !found {
Errorf(s, "%s: missing sect.sym", pn)
}
} else {
@ -403,16 +342,17 @@ func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
if s.Attr.DuplicateOK() {
continue
}
Exitf("%s: duplicate symbol reference: %s in both %s and %s", pn, s.Name, s.Outer.Name, sect.sym.Name)
Exitf("%s: duplicate symbol reference: %s in both %s and %s", pn, s.Name, s.Outer.Name, sectsyms[sect].Name)
}
s.Sub = sect.sym.Sub
sect.sym.Sub = s
s.Type = sect.sym.Type | obj.SSUB
s.Value = int64(sym.value)
sectsym := sectsyms[sect]
s.Sub = sectsym.Sub
sectsym.Sub = s
s.Type = sectsym.Type | obj.SSUB
s.Value = int64(pesym.Value)
s.Size = 4
s.Outer = sect.sym
if sect.sym.Type == obj.STEXT {
s.Outer = sectsym
if sectsym.Type == obj.STEXT {
if s.Attr.External() && !s.Attr.DuplicateOK() {
Errorf(s, "%s: duplicate symbol definition", pn)
}
@ -422,8 +362,8 @@ func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
// Sort outer lists by address, adding to textp.
// This keeps textp in increasing address order.
for i := 0; uint(i) < peobj.nsect; i++ {
s = peobj.sect[i].sym
for _, sect := range f.Sections {
s := sectsyms[sect]
if s == nil {
continue
}
@ -452,43 +392,20 @@ bad:
Errorf(nil, "%s: malformed pe file: %v", pn, err)
}
func pemap(peobj *PeObj, sect *PeSect) int {
if sect.base != nil {
return 0
}
sect.base = make([]byte, sect.sh.SizeOfRawData)
if sect.sh.PointerToRawData == 0 { // .bss doesn't have data in object file
return 0
}
if peobj.f.Seek(int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 {
return -1
}
if _, err := io.ReadFull(peobj.f, sect.base); err != nil {
return -1
}
return 0
func issect(s *pe.COFFSymbol) bool {
return s.StorageClass == IMAGE_SYM_CLASS_STATIC && s.Type == 0 && s.Name[0] == '.'
}
func issect(s *PeSym) bool {
return s.sclass == IMAGE_SYM_CLASS_STATIC && s.type_ == 0 && s.name[0] == '.'
}
func readpesym(ctxt *Link, peobj *PeObj, i int, y **PeSym, localSymVersion int) (err error) {
if uint(i) >= peobj.npesym || i < 0 {
err = fmt.Errorf("invalid pe symbol index")
return err
func readpesym(ctxt *Link, f *pe.File, sym *pe.COFFSymbol, sectsyms map[*pe.Section]*Symbol, localSymVersion int) (*Symbol, error) {
symname, err := sym.FullName(f.StringTable)
if err != nil {
return nil, err
}
sym := &peobj.pesym[i]
*y = sym
var name string
if issect(sym) {
name = peobj.sect[sym.sectnum-1].sym.Name
name = sectsyms[f.Sections[sym.SectionNumber-1]].Name
} else {
name = sym.name
name = symname
if strings.HasPrefix(name, "__imp_") {
name = name[6:] // __imp_Name => Name
}
@ -503,13 +420,12 @@ func readpesym(ctxt *Link, peobj *PeObj, i int, y **PeSym, localSymVersion int)
}
var s *Symbol
switch sym.type_ {
switch sym.Type {
default:
err = fmt.Errorf("%s: invalid symbol type %d", sym.name, sym.type_)
return err
return nil, fmt.Errorf("%s: invalid symbol type %d", symname, sym.Type)
case IMAGE_SYM_DTYPE_FUNCTION, IMAGE_SYM_DTYPE_NULL:
switch sym.sclass {
switch sym.StorageClass {
case IMAGE_SYM_CLASS_EXTERNAL: //global
s = ctxt.Syms.Lookup(name, 0)
@ -518,18 +434,16 @@ func readpesym(ctxt *Link, peobj *PeObj, i int, y **PeSym, localSymVersion int)
s.Attr |= AttrDuplicateOK
default:
err = fmt.Errorf("%s: invalid symbol binding %d", sym.name, sym.sclass)
return err
return nil, fmt.Errorf("%s: invalid symbol binding %d", symname, sym.StorageClass)
}
}
if s != nil && s.Type == 0 && (sym.sclass != IMAGE_SYM_CLASS_STATIC || sym.value != 0) {
if s != nil && s.Type == 0 && (sym.StorageClass != IMAGE_SYM_CLASS_STATIC || sym.Value != 0) {
s.Type = obj.SXREF
}
if strings.HasPrefix(sym.name, "__imp_") {
if strings.HasPrefix(symname, "__imp_") {
s.Got = -2 // flag for __imp_
}
sym.sym = s
return nil
return s, nil
}

View File

@ -0,0 +1,343 @@
// 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.
package pe
import (
"debug/dwarf"
"encoding/binary"
"fmt"
"io"
"os"
)
// A File represents an open PE file.
type File struct {
FileHeader
OptionalHeader interface{} // 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
}
var (
sizeofOptionalHeader32 = uint16(binary.Size(OptionalHeader32{}))
sizeofOptionalHeader64 = uint16(binary.Size(OptionalHeader64{}))
)
// 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 COFF file signature of %v.", sign)
}
base = signoff + 4
} else {
base = int64(0)
}
sr.Seek(base, os.SEEK_SET)
if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
return nil, err
}
switch f.FileHeader.Machine {
case IMAGE_FILE_MACHINE_UNKNOWN, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386:
default:
return nil, fmt.Errorf("Unrecognised COFF file header machine value of 0x%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
}
// Read optional header.
sr.Seek(base, os.SEEK_SET)
if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
return nil, err
}
var oh32 OptionalHeader32
var oh64 OptionalHeader64
switch f.FileHeader.SizeOfOptionalHeader {
case sizeofOptionalHeader32:
if err := binary.Read(sr, binary.LittleEndian, &oh32); err != nil {
return nil, err
}
if oh32.Magic != 0x10b { // PE32
return nil, fmt.Errorf("pe32 optional header has unexpected Magic of 0x%x", oh32.Magic)
}
f.OptionalHeader = &oh32
case sizeofOptionalHeader64:
if err := binary.Read(sr, binary.LittleEndian, &oh64); err != nil {
return nil, err
}
if oh64.Magic != 0x20b { // PE32+
return nil, fmt.Errorf("pe32+ optional header has unexpected Magic of 0x%x", oh64.Magic)
}
f.OptionalHeader = &oh64
}
// 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) {
// There are many other DWARF sections, but these
// are the ones the debug/dwarf package uses.
// Don't bother loading others.
var names = [...]string{"abbrev", "info", "line", "ranges", "str"}
var dat [len(names)][]byte
for i, name := range names {
name = ".debug_" + name
s := f.Section(name)
if s == nil {
continue
}
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]
}
dat[i] = b
}
abbrev, info, line, ranges, str := dat[0], dat[1], dat[2], dat[3], dat[4]
return dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str)
}
// 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) {
pe64 := f.Machine == IMAGE_FILE_MACHINE_AMD64
ds := f.Section(".idata")
if ds == nil {
// not dynamic, so no libraries
return nil, nil
}
d, err := ds.Data()
if err != nil {
return nil, err
}
var ida []ImportDirectory
for len(d) > 0 {
var dt ImportDirectory
dt.OriginalFirstThunk = binary.LittleEndian.Uint32(d[0:4])
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() return contets of .idata section. 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"
}

View File

@ -0,0 +1,417 @@
// 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 (
"debug/dwarf"
"internal/testenv"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"testing"
)
type fileTest struct {
file string
hdr FileHeader
opthdr interface{}
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},
},
},
}
func isOptHdrEq(a, b interface{}) 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)
}
}
func TestDWARF(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("skipping windows only test")
}
tmpdir, err := ioutil.TempDir("", "TestDWARF")
if err != nil {
t.Fatal("TempDir failed: ", err)
}
defer os.RemoveAll(tmpdir)
prog := `
package main
func main() {
}
`
src := filepath.Join(tmpdir, "a.go")
exe := filepath.Join(tmpdir, "a.exe")
err = ioutil.WriteFile(src, []byte(prog), 0644)
output, err := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, src).CombinedOutput()
if err != nil {
t.Fatalf("building test executable failed: %s %s", err, output)
}
f, err := Open(exe)
if err != nil {
t.Fatal(err)
}
defer f.Close()
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 {
for _, f := range e.Field {
if f.Attr == dwarf.AttrName && e.Val(dwarf.AttrName) == "main.main" {
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, err := ioutil.TempDir("", "TestBSSHasZeros")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
srcpath := filepath.Join(tmpdir, "a.c")
src := `
#include <stdio.h>
int zero = 0;
int
main(void)
{
printf("%d\n", zero);
return 0;
}
`
err = ioutil.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)
}
}
}

View File

@ -0,0 +1,110 @@
// 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_EBC = 0xebc
IMAGE_FILE_MACHINE_I386 = 0x14c
IMAGE_FILE_MACHINE_IA64 = 0x200
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
)

View File

@ -0,0 +1,112 @@
// 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"
"io"
"os"
"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), os.SEEK_SET)
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) {
dat := make([]byte, s.sr.Size())
n, err := s.sr.ReadAt(dat, 0)
if n == len(dat) {
err = nil
}
return dat[0:n], err
}
// 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)
}

View File

@ -0,0 +1,67 @@
// 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"
"io"
"os"
)
// cstring converts ASCII byte sequence b to string.
// It stops once it finds 0 or reaches end of b.
func cstring(b []byte) string {
var i int
for i = 0; i < len(b) && b[i] != 0; i++ {
}
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), os.SEEK_SET)
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 := make([]byte, l)
_, err = io.ReadFull(r, buf)
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
}

View File

@ -0,0 +1,96 @@
// 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"
"io"
"os"
)
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
}
func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) {
if fh.NumberOfSymbols <= 0 {
return nil, nil
}
_, err := r.Seek(int64(fh.PointerToSymbolTable), os.SEEK_SET)
if err != nil {
return nil, fmt.Errorf("fail to seek to symbol table: %v", err)
}
syms := make([]COFFSymbol, fh.NumberOfSymbols)
err = binary.Read(r, binary.LittleEndian, syms)
if err != nil {
return nil, fmt.Errorf("fail to read symbol table: %v", err)
}
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
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
#include <stdio.h>
int
main(void)
{
printf("hello, world\n");
return 0;
}

View File

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# 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.
# Run this script to obtain an up-to-date vendored version of debug/pe
PEDIR=../../../../debug/pe
# Start from scratch.
rm testdata/*
rm *.go
rmdir testdata
# Copy all files.
mkdir testdata
cp $PEDIR/*.go .
cp $PEDIR/testdata/* ./testdata
# go1.4 (bootstrap) does not know what io.SeekStart is.
sed -i 's|io.SeekStart|os.SEEK_SET|' *.go
# goimports to clean up after sed
goimports -w .
# gofmt to clean up after sed
gofmt -w .
# Test that it works
go test -short