version: remove rsc.io/goversion dependency

rsc.io/goversion is really expensive.
Running version.ReadExe on tailscaled on darwin
allocates 47k objects, almost 11mb.

All we want is the module info. For that, all we need to do
is scan through the binary looking for the magic start/end strings
and then grab the bytes in between them.

We can do that easily and quickly with nothing but a 64k buffer.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
pull/2609/head
Josh Bleecher Snyder 3 years ago committed by Josh Bleecher Snyder
parent bdb93c5942
commit a4e19f2233

@ -20,7 +20,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
go4.org/unsafe/assume-no-moving-gc from go4.org/intern go4.org/unsafe/assume-no-moving-gc from go4.org/intern
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+ W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
inet.af/netaddr from tailscale.com/cmd/tailscale/cli+ inet.af/netaddr from tailscale.com/cmd/tailscale/cli+
rsc.io/goversion/version from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn tailscale.com/atomicfile from tailscale.com/ipn
tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+ tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+ tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
@ -101,9 +100,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/time/rate from tailscale.com/cmd/tailscale/cli+ golang.org/x/time/rate from tailscale.com/cmd/tailscale/cli+
bufio from compress/flate+ bufio from compress/flate+
bytes from bufio+ bytes from bufio+
compress/flate from compress/gzip+ compress/flate from compress/gzip
compress/gzip from net/http compress/gzip from net/http
compress/zlib from debug/elf+
container/list from crypto/tls+ container/list from crypto/tls+
context from crypto/tls+ context from crypto/tls+
crypto from crypto/ecdsa+ crypto from crypto/ecdsa+
@ -126,10 +124,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
crypto/tls from github.com/tcnksm/go-httpstat+ crypto/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+ crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+ crypto/x509/pkix from crypto/x509+
debug/dwarf from debug/elf+
debug/elf from rsc.io/goversion/version
debug/macho from rsc.io/goversion/version
debug/pe from rsc.io/goversion/version
embed from tailscale.com/cmd/tailscale/cli embed from tailscale.com/cmd/tailscale/cli
encoding from encoding/json+ encoding from encoding/json+
encoding/asn1 from crypto/x509+ encoding/asn1 from crypto/x509+
@ -143,8 +137,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
expvar from tailscale.com/derp+ expvar from tailscale.com/derp+
flag from github.com/peterbourgon/ff/v2+ flag from github.com/peterbourgon/ff/v2+
fmt from compress/flate+ fmt from compress/flate+
hash from compress/zlib+ hash from crypto+
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+ hash/crc32 from compress/gzip+
hash/maphash from go4.org/mem hash/maphash from go4.org/mem
html from tailscale.com/ipn/ipnstate+ html from tailscale.com/ipn/ipnstate+
@ -171,10 +164,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
os/exec from github.com/toqueteos/webbrowser+ os/exec from github.com/toqueteos/webbrowser+
os/signal from tailscale.com/cmd/tailscale/cli os/signal from tailscale.com/cmd/tailscale/cli
os/user from tailscale.com/util/groupmember os/user from tailscale.com/util/groupmember
path from debug/dwarf+ path from html/template+
path/filepath from crypto/x509+ path/filepath from crypto/x509+
reflect from crypto/x509+ reflect from crypto/x509+
regexp from rsc.io/goversion/version+ regexp from github.com/tailscale/goupnp/httpu+
regexp/syntax from regexp regexp/syntax from regexp
runtime/debug from golang.org/x/sync/singleflight runtime/debug from golang.org/x/sync/singleflight
sort from compress/flate+ sort from compress/flate+

@ -87,7 +87,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
inet.af/netstack/waiter from inet.af/netstack/tcpip+ inet.af/netstack/waiter from inet.af/netstack/tcpip+
inet.af/peercred from tailscale.com/ipn/ipnserver inet.af/peercred from tailscale.com/ipn/ipnserver
W 💣 inet.af/wf from tailscale.com/wf W 💣 inet.af/wf from tailscale.com/wf
rsc.io/goversion/version from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/atomicfile from tailscale.com/ipn+
tailscale.com/client/tailscale from tailscale.com/derp tailscale.com/client/tailscale from tailscale.com/derp
tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnlocal+ tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnlocal+
@ -216,9 +215,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/time/rate from inet.af/netstack/tcpip/stack+ golang.org/x/time/rate from inet.af/netstack/tcpip/stack+
bufio from compress/flate+ bufio from compress/flate+
bytes from bufio+ bytes from bufio+
compress/flate from compress/gzip+ compress/flate from compress/gzip
compress/gzip from internal/profile+ compress/gzip from internal/profile+
compress/zlib from debug/elf+
container/heap from inet.af/netstack/tcpip/transport/tcp container/heap from inet.af/netstack/tcpip/transport/tcp
container/list from crypto/tls+ container/list from crypto/tls+
context from crypto/tls+ context from crypto/tls+
@ -242,10 +240,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
crypto/tls from github.com/tcnksm/go-httpstat+ crypto/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+ crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+ crypto/x509/pkix from crypto/x509+
debug/dwarf from debug/elf+
debug/elf from rsc.io/goversion/version
debug/macho from rsc.io/goversion/version
debug/pe from rsc.io/goversion/version
embed from tailscale.com/net/dns+ embed from tailscale.com/net/dns+
encoding from encoding/json+ encoding from encoding/json+
encoding/asn1 from crypto/x509+ encoding/asn1 from crypto/x509+
@ -259,8 +253,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
expvar from tailscale.com/derp+ expvar from tailscale.com/derp+
flag from tailscale.com/cmd/tailscaled+ flag from tailscale.com/cmd/tailscaled+
fmt from compress/flate+ fmt from compress/flate+
hash from compress/zlib+ hash from crypto+
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+ hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock+ hash/fnv from tailscale.com/wgengine/magicsock+
hash/maphash from go4.org/mem hash/maphash from go4.org/mem
@ -288,7 +281,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
os/exec from github.com/coreos/go-iptables/iptables+ os/exec from github.com/coreos/go-iptables/iptables+
os/signal from tailscale.com/cmd/tailscaled+ os/signal from tailscale.com/cmd/tailscaled+
os/user from github.com/godbus/dbus/v5+ os/user from github.com/godbus/dbus/v5+
path from debug/dwarf+ path from github.com/godbus/dbus/v5+
path/filepath from crypto/x509+ path/filepath from crypto/x509+
reflect from crypto/x509+ reflect from crypto/x509+
regexp from github.com/coreos/go-iptables/iptables+ regexp from github.com/coreos/go-iptables/iptables+

@ -51,5 +51,4 @@ require (
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e inet.af/netstack v0.0.0-20210622165351-29b14ebc044e
inet.af/peercred v0.0.0-20210318190834-4259e17bb763 inet.af/peercred v0.0.0-20210318190834-4259e17bb763
inet.af/wf v0.0.0-20210516214145-a5343001b756 inet.af/wf v0.0.0-20210516214145-a5343001b756
rsc.io/goversion v1.2.0
) )

@ -8,12 +8,14 @@
package version package version
import ( import (
"bytes"
"encoding/hex"
"errors"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"rsc.io/goversion/version"
) )
// CmdName returns either the base name of the current binary // CmdName returns either the base name of the current binary
@ -30,13 +32,13 @@ func CmdName() string {
fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(e), ".exe")) fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(e), ".exe"))
var ret string var ret string
v, err := version.ReadExe(e) info, err := findModuleInfo(e)
if err != nil { if err != nil {
return fallbackName return fallbackName
} }
// v is like: // v is like:
// "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub.... // "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub....
for _, line := range strings.Split(v.ModuleInfo, "\n") { for _, line := range strings.Split(info, "\n") {
if strings.HasPrefix(line, "path\t") { if strings.HasPrefix(line, "path\t") {
goPkg := strings.TrimPrefix(line, "path\t") // like "tailscale.com/cmd/tailscale" goPkg := strings.TrimPrefix(line, "path\t") // like "tailscale.com/cmd/tailscale"
ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath
@ -48,3 +50,84 @@ func CmdName() string {
} }
return ret return ret
} }
// findModuleInfo returns the Go module info from the executable file.
func findModuleInfo(file string) (s string, err error) {
f, err := os.Open(file)
if err != nil {
return "", err
}
defer f.Close()
// Scan through f until we find infoStart.
buf := make([]byte, 65536)
start, err := findOffset(f, buf, infoStart)
if err != nil {
return "", err
}
start += int64(len(infoStart))
// Seek to the end of infoStart and scan for infoEnd.
_, err = f.Seek(start, io.SeekStart)
if err != nil {
return "", err
}
end, err := findOffset(f, buf, infoEnd)
if err != nil {
return "", err
}
length := end - start
// As of Aug 2021, tailscaled's mod info was about 2k.
if length > int64(len(buf)) {
return "", errors.New("mod info too large")
}
// We have located modinfo. Read it into buf.
buf = buf[:length]
_, err = f.Seek(start, io.SeekStart)
if err != nil {
return "", err
}
_, err = io.ReadFull(f, buf)
if err != nil {
return "", err
}
return string(buf), nil
}
// findOffset finds the absolute offset of needle in f,
// starting at f's current read position,
// using temporary buffer buf.
func findOffset(f *os.File, buf, needle []byte) (int64, error) {
for {
// Fill buf and look within it.
n, err := f.Read(buf)
if err != nil {
return -1, err
}
i := bytes.Index(buf[:n], needle)
if i < 0 {
// Not found. Rewind a little bit in case we happened to end halfway through needle.
rewind, err := f.Seek(int64(-len(needle)), io.SeekCurrent)
if err != nil {
return -1, err
}
// If we're at EOF and rewound exactly len(needle) bytes, return io.EOF.
_, err = f.ReadAt(buf[:1], rewind+int64(len(needle)))
if err == io.EOF {
return -1, err
}
continue
}
// Found! Figure out exactly where.
cur, err := f.Seek(0, io.SeekCurrent)
if err != nil {
return -1, err
}
return cur - int64(n) + int64(i), nil
}
}
// These constants are taken from rsc.io/goversion.
var (
infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
)

@ -0,0 +1,29 @@
// Copyright (c) 2021 Tailscale Inc & 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 (
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestFindModuleInfo(t *testing.T) {
dir := t.TempDir()
name := filepath.Join(dir, "tailscaled-version-test")
out, err := exec.Command("go", "build", "-o", name, "tailscale.com/cmd/tailscaled").CombinedOutput()
if err != nil {
t.Fatalf("failed to build tailscaled: %v\n%s", err, out)
}
modinfo, err := findModuleInfo(name)
if err != nil {
t.Fatal(err)
}
prefix := "path\ttailscale.com/cmd/tailscaled\nmod\ttailscale.com"
if !strings.HasPrefix(modinfo, prefix) {
t.Errorf("unexpected modinfo contents %q", modinfo)
}
}
Loading…
Cancel
Save