+++ /dev/null
-// 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
-}
import (
"bytes"
"context"
- "encoding/binary"
+ "debug/buildinfo"
+ "errors"
"fmt"
"io/fs"
"os"
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)
}