]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: migrate 'go version' to use buildinfo.ReadFile
authorJay Conrod <jayconrod@google.com>
Thu, 30 Sep 2021 20:16:33 +0000 (13:16 -0700)
committerJay Conrod <jayconrod@google.com>
Thu, 14 Oct 2021 18:44:29 +0000 (18:44 +0000)
The same code was copied into debug/buildinfo. 'go version' doesn't
need its own copy.

For #37475

Change-Id: I9e473ce574139a87a5f9c63229f0fc7ffac447a0
Reviewed-on: https://go-review.googlesource.com/c/go/+/353929
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
src/cmd/go/internal/version/exe.go [deleted file]
src/cmd/go/internal/version/version.go

diff --git a/src/cmd/go/internal/version/exe.go b/src/cmd/go/internal/version/exe.go
deleted file mode 100644 (file)
index 0e7deef..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2019 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 version
-
-import (
-       "bytes"
-       "debug/elf"
-       "debug/macho"
-       "debug/pe"
-       "fmt"
-       "internal/xcoff"
-       "io"
-       "os"
-)
-
-// An exe is a generic interface to an OS executable (ELF, Mach-O, PE, XCOFF).
-type exe interface {
-       // Close closes the underlying file.
-       Close() error
-
-       // ReadData reads and returns up to size byte starting at virtual address addr.
-       ReadData(addr, size uint64) ([]byte, error)
-
-       // DataStart returns the writable data segment start address.
-       DataStart() uint64
-}
-
-// openExe opens file and returns it as an exe.
-func openExe(file string) (exe, error) {
-       f, err := os.Open(file)
-       if err != nil {
-               return nil, err
-       }
-       data := make([]byte, 16)
-       if _, err := io.ReadFull(f, data); err != nil {
-               return nil, err
-       }
-       f.Seek(0, 0)
-       if bytes.HasPrefix(data, []byte("\x7FELF")) {
-               e, err := elf.NewFile(f)
-               if err != nil {
-                       f.Close()
-                       return nil, err
-               }
-               return &elfExe{f, e}, nil
-       }
-       if bytes.HasPrefix(data, []byte("MZ")) {
-               e, err := pe.NewFile(f)
-               if err != nil {
-                       f.Close()
-                       return nil, err
-               }
-               return &peExe{f, e}, nil
-       }
-       if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) {
-               e, err := macho.NewFile(f)
-               if err != nil {
-                       f.Close()
-                       return nil, err
-               }
-               return &machoExe{f, e}, nil
-       }
-       if bytes.HasPrefix(data, []byte{0x01, 0xDF}) || bytes.HasPrefix(data, []byte{0x01, 0xF7}) {
-               e, err := xcoff.NewFile(f)
-               if err != nil {
-                       f.Close()
-                       return nil, err
-               }
-               return &xcoffExe{f, e}, nil
-
-       }
-       return nil, fmt.Errorf("unrecognized executable format")
-}
-
-// elfExe is the ELF implementation of the exe interface.
-type elfExe struct {
-       os *os.File
-       f  *elf.File
-}
-
-func (x *elfExe) Close() error {
-       return x.os.Close()
-}
-
-func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
-       for _, prog := range x.f.Progs {
-               if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
-                       n := prog.Vaddr + prog.Filesz - addr
-                       if n > size {
-                               n = size
-                       }
-                       data := make([]byte, n)
-                       _, err := prog.ReadAt(data, int64(addr-prog.Vaddr))
-                       if err != nil {
-                               return nil, err
-                       }
-                       return data, nil
-               }
-       }
-       return nil, fmt.Errorf("address not mapped")
-}
-
-func (x *elfExe) DataStart() uint64 {
-       for _, s := range x.f.Sections {
-               if s.Name == ".go.buildinfo" {
-                       return s.Addr
-               }
-       }
-       for _, p := range x.f.Progs {
-               if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
-                       return p.Vaddr
-               }
-       }
-       return 0
-}
-
-// peExe is the PE (Windows Portable Executable) implementation of the exe interface.
-type peExe struct {
-       os *os.File
-       f  *pe.File
-}
-
-func (x *peExe) Close() error {
-       return x.os.Close()
-}
-
-func (x *peExe) imageBase() uint64 {
-       switch oh := x.f.OptionalHeader.(type) {
-       case *pe.OptionalHeader32:
-               return uint64(oh.ImageBase)
-       case *pe.OptionalHeader64:
-               return oh.ImageBase
-       }
-       return 0
-}
-
-func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
-       addr -= x.imageBase()
-       for _, sect := range x.f.Sections {
-               if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
-                       n := uint64(sect.VirtualAddress+sect.Size) - addr
-                       if n > size {
-                               n = size
-                       }
-                       data := make([]byte, n)
-                       _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
-                       if err != nil {
-                               return nil, err
-                       }
-                       return data, nil
-               }
-       }
-       return nil, fmt.Errorf("address not mapped")
-}
-
-func (x *peExe) DataStart() uint64 {
-       // Assume data is first writable section.
-       const (
-               IMAGE_SCN_CNT_CODE               = 0x00000020
-               IMAGE_SCN_CNT_INITIALIZED_DATA   = 0x00000040
-               IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
-               IMAGE_SCN_MEM_EXECUTE            = 0x20000000
-               IMAGE_SCN_MEM_READ               = 0x40000000
-               IMAGE_SCN_MEM_WRITE              = 0x80000000
-               IMAGE_SCN_MEM_DISCARDABLE        = 0x2000000
-               IMAGE_SCN_LNK_NRELOC_OVFL        = 0x1000000
-               IMAGE_SCN_ALIGN_32BYTES          = 0x600000
-       )
-       for _, sect := range x.f.Sections {
-               if sect.VirtualAddress != 0 && sect.Size != 0 &&
-                       sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
-                       return uint64(sect.VirtualAddress) + x.imageBase()
-               }
-       }
-       return 0
-}
-
-// machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
-type machoExe struct {
-       os *os.File
-       f  *macho.File
-}
-
-func (x *machoExe) Close() error {
-       return x.os.Close()
-}
-
-func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
-       for _, load := range x.f.Loads {
-               seg, ok := load.(*macho.Segment)
-               if !ok {
-                       continue
-               }
-               if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
-                       if seg.Name == "__PAGEZERO" {
-                               continue
-                       }
-                       n := seg.Addr + seg.Filesz - addr
-                       if n > size {
-                               n = size
-                       }
-                       data := make([]byte, n)
-                       _, err := seg.ReadAt(data, int64(addr-seg.Addr))
-                       if err != nil {
-                               return nil, err
-                       }
-                       return data, nil
-               }
-       }
-       return nil, fmt.Errorf("address not mapped")
-}
-
-func (x *machoExe) DataStart() uint64 {
-       // Look for section named "__go_buildinfo".
-       for _, sec := range x.f.Sections {
-               if sec.Name == "__go_buildinfo" {
-                       return sec.Addr
-               }
-       }
-       // Try the first non-empty writable segment.
-       const RW = 3
-       for _, load := range x.f.Loads {
-               seg, ok := load.(*macho.Segment)
-               if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
-                       return seg.Addr
-               }
-       }
-       return 0
-}
-
-// xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface.
-type xcoffExe struct {
-       os *os.File
-       f  *xcoff.File
-}
-
-func (x *xcoffExe) Close() error {
-       return x.os.Close()
-}
-
-func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) {
-       for _, sect := range x.f.Sections {
-               if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
-                       n := uint64(sect.VirtualAddress+sect.Size) - addr
-                       if n > size {
-                               n = size
-                       }
-                       data := make([]byte, n)
-                       _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
-                       if err != nil {
-                               return nil, err
-                       }
-                       return data, nil
-               }
-       }
-       return nil, fmt.Errorf("address not mapped")
-}
-
-func (x *xcoffExe) DataStart() uint64 {
-       return x.f.SectionByType(xcoff.STYP_DATA).VirtualAddress
-}
index e885933ac3381196b7c6ced2c52de66e98ff5852..febc7c638ae9ed33f61d09022570af2eb80faecf 100644 (file)
@@ -8,7 +8,8 @@ package version
 import (
        "bytes"
        "context"
-       "encoding/binary"
+       "debug/buildinfo"
+       "errors"
        "fmt"
        "io/fs"
        "os"
@@ -141,90 +142,25 @@ func scanFile(file string, info fs.FileInfo, mustPrint bool) {
                return
        }
 
-       x, err := openExe(file)
+       bi, err := buildinfo.ReadFile(file)
        if err != nil {
                if mustPrint {
-                       fmt.Fprintf(os.Stderr, "%s: %v\n", file, err)
+                       if pathErr := (*os.PathError)(nil); errors.As(err, &pathErr) && filepath.Clean(pathErr.Path) == filepath.Clean(file) {
+                               fmt.Fprintf(os.Stderr, "%v\n", file)
+                       } else {
+                               fmt.Fprintf(os.Stderr, "%s: %v\n", file, err)
+                       }
                }
-               return
        }
-       defer x.Close()
 
-       vers, mod := findVers(x)
-       if vers == "" {
-               if mustPrint {
-                       fmt.Fprintf(os.Stderr, "%s: go version not found\n", file)
-               }
-               return
-       }
-
-       fmt.Printf("%s: %s\n", file, vers)
-       if *versionM && mod != "" {
-               fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t"))
-       }
-}
-
-// The build info blob left by the linker is identified by
-// a 16-byte header, consisting of buildInfoMagic (14 bytes),
-// the binary's pointer size (1 byte),
-// and whether the binary is big endian (1 byte).
-var buildInfoMagic = []byte("\xff Go buildinf:")
-
-// findVers finds and returns the Go version and module version information
-// in the executable x.
-func findVers(x exe) (vers, mod string) {
-       // Read the first 64kB of text to find the build info blob.
-       text := x.DataStart()
-       data, err := x.ReadData(text, 64*1024)
+       fmt.Printf("%s: %s\n", file, bi.GoVersion)
+       bi.GoVersion = "" // suppress printing go version again
+       mod, err := bi.MarshalText()
        if err != nil {
+               fmt.Fprintf(os.Stderr, "%s: formatting build info: %v\n", file, err)
                return
        }
-       for ; !bytes.HasPrefix(data, buildInfoMagic); data = data[32:] {
-               if len(data) < 32 {
-                       return
-               }
-       }
-
-       // Decode the blob.
-       ptrSize := int(data[14])
-       bigEndian := data[15] != 0
-       var bo binary.ByteOrder
-       if bigEndian {
-               bo = binary.BigEndian
-       } else {
-               bo = binary.LittleEndian
-       }
-       var readPtr func([]byte) uint64
-       if ptrSize == 4 {
-               readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
-       } else {
-               readPtr = bo.Uint64
-       }
-       vers = readString(x, ptrSize, readPtr, readPtr(data[16:]))
-       if vers == "" {
-               return
-       }
-       mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:]))
-       if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
-               // Strip module framing.
-               mod = mod[16 : len(mod)-16]
-       } else {
-               mod = ""
-       }
-       return
-}
-
-// readString returns the string at address addr in the executable x.
-func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string {
-       hdr, err := x.ReadData(addr, uint64(2*ptrSize))
-       if err != nil || len(hdr) < 2*ptrSize {
-               return ""
-       }
-       dataAddr := readPtr(hdr)
-       dataLen := readPtr(hdr[ptrSize:])
-       data, err := x.ReadData(dataAddr, dataLen)
-       if err != nil || uint64(len(data)) < dataLen {
-               return ""
+       if *versionM && len(mod) > 0 {
+               fmt.Printf("\t%s\n", bytes.ReplaceAll(mod[:len(mod)-1], []byte("\n"), []byte("\n\t")))
        }
-       return string(data)
 }