mirror of https://github.com/golang/go.git
cmd/go: migrate 'go version' to use buildinfo.ReadFile
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>
This commit is contained in:
parent
85a068fdf2
commit
a8c5a994d6
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,8 @@ package version
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"debug/buildinfo"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -141,90 +142,25 @@ func scanFile(file string, info fs.FileInfo, mustPrint bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := openExe(file)
|
bi, err := buildinfo.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if mustPrint {
|
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)
|
fmt.Printf("%s: %s\n", file, bi.GoVersion)
|
||||||
if *versionM && mod != "" {
|
bi.GoVersion = "" // suppress printing go version again
|
||||||
fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t"))
|
mod, err := bi.MarshalText()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: formatting build info: %v\n", file, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for ; !bytes.HasPrefix(data, buildInfoMagic); data = data[32:] {
|
if *versionM && len(mod) > 0 {
|
||||||
if len(data) < 32 {
|
fmt.Printf("\t%s\n", bytes.ReplaceAll(mod[:len(mod)-1], []byte("\n"), []byte("\n\t")))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue