summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/version
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:14:23 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:14:23 +0000
commit73df946d56c74384511a194dd01dbe099584fd1a (patch)
treefd0bcea490dd81327ddfbb31e215439672c9a068 /src/cmd/go/internal/version
parentInitial commit. (diff)
downloadgolang-1.16-upstream.tar.xz
golang-1.16-upstream.zip
Adding upstream version 1.16.10.upstream/1.16.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/go/internal/version')
-rw-r--r--src/cmd/go/internal/version/exe.go263
-rw-r--r--src/cmd/go/internal/version/version.go224
2 files changed, 487 insertions, 0 deletions
diff --git a/src/cmd/go/internal/version/exe.go b/src/cmd/go/internal/version/exe.go
new file mode 100644
index 0000000..0e7deef
--- /dev/null
+++ b/src/cmd/go/internal/version/exe.go
@@ -0,0 +1,263 @@
+// 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
+}
diff --git a/src/cmd/go/internal/version/version.go b/src/cmd/go/internal/version/version.go
new file mode 100644
index 0000000..58cbd32
--- /dev/null
+++ b/src/cmd/go/internal/version/version.go
@@ -0,0 +1,224 @@
+// Copyright 2011 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 implements the ``go version'' command.
+package version
+
+import (
+ "bytes"
+ "context"
+ "encoding/binary"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "cmd/go/internal/base"
+)
+
+var CmdVersion = &base.Command{
+ UsageLine: "go version [-m] [-v] [file ...]",
+ Short: "print Go version",
+ Long: `Version prints the build information for Go executables.
+
+Go version reports the Go version used to build each of the named
+executable files.
+
+If no files are named on the command line, go version prints its own
+version information.
+
+If a directory is named, go version walks that directory, recursively,
+looking for recognized Go binaries and reporting their versions.
+By default, go version does not report unrecognized files found
+during a directory scan. The -v flag causes it to report unrecognized files.
+
+The -m flag causes go version to print each executable's embedded
+module version information, when available. In the output, the module
+information consists of multiple lines following the version line, each
+indented by a leading tab character.
+
+See also: go doc runtime/debug.BuildInfo.
+`,
+}
+
+func init() {
+ CmdVersion.Run = runVersion // break init cycle
+}
+
+var (
+ versionM = CmdVersion.Flag.Bool("m", false, "")
+ versionV = CmdVersion.Flag.Bool("v", false, "")
+)
+
+func runVersion(ctx context.Context, cmd *base.Command, args []string) {
+ if len(args) == 0 {
+ // If any of this command's flags were passed explicitly, error
+ // out, because they only make sense with arguments.
+ //
+ // Don't error if the flags came from GOFLAGS, since that can be
+ // a reasonable use case. For example, imagine GOFLAGS=-v to
+ // turn "verbose mode" on for all Go commands, which should not
+ // break "go version".
+ if (!base.InGOFLAGS("-m") && *versionM) || (!base.InGOFLAGS("-v") && *versionV) {
+ fmt.Fprintf(os.Stderr, "go version: flags can only be used with arguments\n")
+ base.SetExitStatus(2)
+ return
+ }
+ fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
+ return
+ }
+
+ for _, arg := range args {
+ info, err := os.Stat(arg)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ base.SetExitStatus(1)
+ continue
+ }
+ if info.IsDir() {
+ scanDir(arg)
+ } else {
+ scanFile(arg, info, true)
+ }
+ }
+}
+
+// scanDir scans a directory for executables to run scanFile on.
+func scanDir(dir string) {
+ filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+ if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 {
+ info, err := d.Info()
+ if err != nil {
+ if *versionV {
+ fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
+ }
+ return nil
+ }
+ scanFile(path, info, *versionV)
+ }
+ return nil
+ })
+}
+
+// isExe reports whether the file should be considered executable.
+func isExe(file string, info fs.FileInfo) bool {
+ if runtime.GOOS == "windows" {
+ return strings.HasSuffix(strings.ToLower(file), ".exe")
+ }
+ return info.Mode().IsRegular() && info.Mode()&0111 != 0
+}
+
+// scanFile scans file to try to report the Go and module versions.
+// If mustPrint is true, scanFile will report any error reading file.
+// Otherwise (mustPrint is false, because scanFile is being called
+// by scanDir) scanFile prints nothing for non-Go executables.
+func scanFile(file string, info fs.FileInfo, mustPrint bool) {
+ if info.Mode()&fs.ModeSymlink != 0 {
+ // Accept file symlinks only.
+ i, err := os.Stat(file)
+ if err != nil || !i.Mode().IsRegular() {
+ if mustPrint {
+ fmt.Fprintf(os.Stderr, "%s: symlink\n", file)
+ }
+ return
+ }
+ info = i
+ }
+
+ if !isExe(file, info) {
+ if mustPrint {
+ fmt.Fprintf(os.Stderr, "%s: not executable file\n", file)
+ }
+ return
+ }
+
+ x, err := openExe(file)
+ if err != nil {
+ if mustPrint {
+ 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)
+ if err != nil {
+ 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 ""
+ }
+ return string(data)
+}