mirror of
https://github.com/golang/go
synced 2024-11-17 20:54:48 -07:00
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 (
|
||||
"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
|
||||
}
|
||||
if *versionM && len(mod) > 0 {
|
||||
fmt.Printf("\t%s\n", bytes.ReplaceAll(mod[:len(mod)-1], []byte("\n"), []byte("\n\t")))
|
||||
}
|
||||
|
||||
// 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
Block a user