From: Alex Brainman Date: Fri, 29 Apr 2016 06:23:55 +0000 (+1000) Subject: cmd/link/internal/ld: use debug/pe package to rewrite ldpe.go X-Git-Tag: go1.8beta1~881 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=d08c3d1329994ddba2e0a2dd13b950f9f178ab02;p=gostls13.git 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 Run-TryBot: Russ Cox TryBot-Result: Gobot Gobot --- diff --git a/misc/nacl/testzip.proto b/misc/nacl/testzip.proto index 561df80642..33218dde8b 100644 --- a/misc/nacl/testzip.proto +++ b/misc/nacl/testzip.proto @@ -36,6 +36,11 @@ go src=.. gofmt_test.go testdata + + link + internal + pe + testdata + + vendor golang.org x diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 13a4f90c30..c700a5d8c9 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -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", diff --git a/src/cmd/link/internal/ld/ldpe.go b/src/cmd/link/internal/ld/ldpe.go index 43d33c7b19..9d9eedad24 100644 --- a/src/cmd/link/internal/ld/ldpe.go +++ b/src/cmd/link/internal/ld/ldpe.go @@ -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 -type PeSect struct { - name string - base []byte - size uint64 - sym *Symbol - sh IMAGE_SECTION_HEADER -} +// peBiobuf makes bio.Reader look like io.ReaderAt. +type peBiobuf bio.Reader -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 (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 } -func ldpe(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) { +// TODO(brainman): remove 'goto bad' everywhere inside ldpe + +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 { - goto bad - } - // load section list - peobj.sect = make([]PeSect, peobj.fh.NumberOfSections) + // 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) - 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[:]) - } - - // 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 { + // 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 } + defer f.Close() - // 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 - } - } + // TODO return error if found .cormeta // 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(pesym) { continue } - if issect(&peobj.pesym[i]) { + if int(pesym.SectionNumber) > len(f.Sections) { continue } - if uint(peobj.pesym[i].sectnum) > peobj.nsect { + if pesym.SectionNumber == IMAGE_SYM_DEBUG { continue } - if peobj.pesym[i].sectnum > 0 { - sect = &peobj.sect[peobj.pesym[i].sectnum-1] - if sect.sym == nil { + 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 *PeSym) bool { - return s.sclass == IMAGE_SYM_CLASS_STATIC && s.type_ == 0 && s.name[0] == '.' +func issect(s *pe.COFFSymbol) bool { + return s.StorageClass == 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 } diff --git a/src/cmd/link/internal/pe/file.go b/src/cmd/link/internal/pe/file.go new file mode 100644 index 0000000000..6a1e1afdf2 --- /dev/null +++ b/src/cmd/link/internal/pe/file.go @@ -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" +} diff --git a/src/cmd/link/internal/pe/file_test.go b/src/cmd/link/internal/pe/file_test.go new file mode 100644 index 0000000000..5a740c8705 --- /dev/null +++ b/src/cmd/link/internal/pe/file_test.go @@ -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 + +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) + } + } +} diff --git a/src/cmd/link/internal/pe/pe.go b/src/cmd/link/internal/pe/pe.go new file mode 100644 index 0000000000..8050d59c70 --- /dev/null +++ b/src/cmd/link/internal/pe/pe.go @@ -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 +) diff --git a/src/cmd/link/internal/pe/section.go b/src/cmd/link/internal/pe/section.go new file mode 100644 index 0000000000..fc6c062d62 --- /dev/null +++ b/src/cmd/link/internal/pe/section.go @@ -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) +} diff --git a/src/cmd/link/internal/pe/string.go b/src/cmd/link/internal/pe/string.go new file mode 100644 index 0000000000..dd0df7f3fd --- /dev/null +++ b/src/cmd/link/internal/pe/string.go @@ -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 +} diff --git a/src/cmd/link/internal/pe/symbol.go b/src/cmd/link/internal/pe/symbol.go new file mode 100644 index 0000000000..16ca91e58e --- /dev/null +++ b/src/cmd/link/internal/pe/symbol.go @@ -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 +} diff --git a/src/cmd/link/internal/pe/testdata/gcc-386-mingw-exec b/src/cmd/link/internal/pe/testdata/gcc-386-mingw-exec new file mode 100644 index 0000000000..4b808d0432 Binary files /dev/null and b/src/cmd/link/internal/pe/testdata/gcc-386-mingw-exec differ diff --git a/src/cmd/link/internal/pe/testdata/gcc-386-mingw-no-symbols-exec b/src/cmd/link/internal/pe/testdata/gcc-386-mingw-no-symbols-exec new file mode 100644 index 0000000000..329dca60b9 Binary files /dev/null and b/src/cmd/link/internal/pe/testdata/gcc-386-mingw-no-symbols-exec differ diff --git a/src/cmd/link/internal/pe/testdata/gcc-386-mingw-obj b/src/cmd/link/internal/pe/testdata/gcc-386-mingw-obj new file mode 100644 index 0000000000..0c84d898d5 Binary files /dev/null and b/src/cmd/link/internal/pe/testdata/gcc-386-mingw-obj differ diff --git a/src/cmd/link/internal/pe/testdata/gcc-amd64-mingw-exec b/src/cmd/link/internal/pe/testdata/gcc-amd64-mingw-exec new file mode 100644 index 0000000000..ce6feb6b7b Binary files /dev/null and b/src/cmd/link/internal/pe/testdata/gcc-amd64-mingw-exec differ diff --git a/src/cmd/link/internal/pe/testdata/gcc-amd64-mingw-obj b/src/cmd/link/internal/pe/testdata/gcc-amd64-mingw-obj new file mode 100644 index 0000000000..48ae7921f3 Binary files /dev/null and b/src/cmd/link/internal/pe/testdata/gcc-amd64-mingw-obj differ diff --git a/src/cmd/link/internal/pe/testdata/hello.c b/src/cmd/link/internal/pe/testdata/hello.c new file mode 100644 index 0000000000..a689d3644e --- /dev/null +++ b/src/cmd/link/internal/pe/testdata/hello.c @@ -0,0 +1,8 @@ +#include + +int +main(void) +{ + printf("hello, world\n"); + return 0; +} diff --git a/src/cmd/link/internal/pe/vendor.bash b/src/cmd/link/internal/pe/vendor.bash new file mode 100755 index 0000000000..8a27ec8797 --- /dev/null +++ b/src/cmd/link/internal/pe/vendor.bash @@ -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