├── go.mod ├── README.md ├── LICENSE ├── version ├── read.go ├── exe.go └── asm.go └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module rsc.io/goversion 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsc.io/goversion 2 | 3 | Goversion scans a directory tree and, for every executable it finds, prints 4 | the Go version used to build that executable. 5 | 6 | Usage: 7 | 8 | goversion [-crypto] [-m | -mh] [-v] path... 9 | 10 | The list of paths can be individual files or directories; if the latter, 11 | goversion scans all files in the directory tree, not following symlinks. 12 | 13 | Goversion scans inside of tar or gzipped tar archives that it finds (named 14 | `*.tar`, `*.tar.gz`, or `*.tgz`), but not recursively. 15 | 16 | The `-crypto` flag causes goversion to print additional information about the 17 | crypto libraries linked into each executable. 18 | 19 | The -m flag causes goversion to print the list of modules 20 | found in the executable, along with version information. 21 | 22 | The -mh flag causes goversion to print the list of modules 23 | found in the executable, along with version and hash information. 24 | 25 | The `-v` flag causes goversion to print information about every file it 26 | considers. 27 | 28 | ## Example 29 | 30 | Scan /usr/bin for Go binaries and print their versions: 31 | 32 | $ goversion /usr/bin 33 | /usr/bin/containerd go1.7.4 34 | /usr/bin/containerd-shim go1.7.4 35 | /usr/bin/ctr go1.7.4 36 | /usr/bin/docker go1.7.4 37 | /usr/bin/docker-proxy go1.7.4 38 | /usr/bin/dockerd go1.7.4 39 | /usr/bin/kbfsfuse go1.8.3 40 | /usr/bin/kbnm go1.8.3 41 | /usr/bin/keybase go1.8.3 42 | /usr/bin/snap go1.7.4 43 | /usr/bin/snapctl go1.7.4 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /version/read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All Rights Reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package version reports the Go version used to build program executables. 6 | package version 7 | 8 | import ( 9 | "bytes" 10 | "encoding/hex" 11 | "errors" 12 | "fmt" 13 | "regexp" 14 | "strings" 15 | ) 16 | 17 | // Version is the information reported by ReadExe. 18 | type Version struct { 19 | Release string // Go version (runtime.Version in the program) 20 | ModuleInfo string // program's module information 21 | BoringCrypto bool // program uses BoringCrypto 22 | StandardCrypto bool // program uses standard crypto (replaced by BoringCrypto) 23 | FIPSOnly bool // program imports "crypto/tls/fipsonly" 24 | } 25 | 26 | // ReadExe reports information about the Go version used to build 27 | // the program executable named by file. 28 | func ReadExe(file string) (Version, error) { 29 | var v Version 30 | f, err := openExe(file) 31 | if err != nil { 32 | return v, err 33 | } 34 | defer f.Close() 35 | isGo := false 36 | for _, name := range f.SectionNames() { 37 | if name == ".note.go.buildid" { 38 | isGo = true 39 | } 40 | } 41 | syms, symsErr := f.Symbols() 42 | isGccgo := false 43 | for _, sym := range syms { 44 | name := sym.Name 45 | if name == "runtime.main" || name == "main.main" { 46 | isGo = true 47 | } 48 | if strings.HasPrefix(name, "runtime.") && strings.HasSuffix(name, "$descriptor") { 49 | isGccgo = true 50 | } 51 | if name == "runtime.buildVersion" { 52 | isGo = true 53 | release, err := readBuildVersion(f, sym.Addr, sym.Size) 54 | if err != nil { 55 | return v, err 56 | } 57 | v.Release = release 58 | } 59 | // Note: Using strings.HasPrefix because Go 1.17+ adds ".abi0" to many of these symbols. 60 | if strings.Contains(name, "_Cfunc__goboringcrypto_") || strings.HasPrefix(name, "crypto/internal/boring/sig.BoringCrypto") { 61 | v.BoringCrypto = true 62 | } 63 | if strings.HasPrefix(name, "crypto/internal/boring/sig.FIPSOnly") { 64 | v.FIPSOnly = true 65 | } 66 | for _, re := range standardCryptoNames { 67 | if re.MatchString(name) { 68 | v.StandardCrypto = true 69 | } 70 | } 71 | if strings.HasPrefix(name, "crypto/internal/boring/sig.StandardCrypto") { 72 | v.StandardCrypto = true 73 | } 74 | } 75 | 76 | if DebugMatch { 77 | v.Release = "" 78 | } 79 | if err := findModuleInfo(&v, f); err != nil { 80 | return v, err 81 | } 82 | if v.Release == "" { 83 | g, release := readBuildVersionX86Asm(f) 84 | if g { 85 | isGo = true 86 | v.Release = release 87 | if err := findCryptoSigs(&v, f); err != nil { 88 | return v, err 89 | } 90 | } 91 | } 92 | if isGccgo && v.Release == "" { 93 | isGo = true 94 | v.Release = "gccgo (version unknown)" 95 | } 96 | if !isGo && symsErr != nil { 97 | return v, symsErr 98 | } 99 | 100 | if !isGo { 101 | return v, errors.New("not a Go executable") 102 | } 103 | if v.Release == "" { 104 | v.Release = "unknown Go version" 105 | } 106 | return v, nil 107 | } 108 | 109 | var re = regexp.MustCompile 110 | 111 | var standardCryptoNames = []*regexp.Regexp{ 112 | re(`^crypto/sha1\.\(\*digest\)`), 113 | re(`^crypto/sha256\.\(\*digest\)`), 114 | re(`^crypto/rand\.\(\*devReader\)`), 115 | re(`^crypto/rsa\.encrypt(\.abi.)?$`), 116 | re(`^crypto/rsa\.decrypt(\.abi.)?$`), 117 | } 118 | 119 | func readBuildVersion(f exe, addr, size uint64) (string, error) { 120 | if size == 0 { 121 | size = uint64(f.AddrSize() * 2) 122 | } 123 | if size != 8 && size != 16 { 124 | return "", fmt.Errorf("invalid size for runtime.buildVersion") 125 | } 126 | data, err := f.ReadData(addr, size) 127 | if err != nil { 128 | return "", fmt.Errorf("reading runtime.buildVersion: %v", err) 129 | } 130 | 131 | if size == 8 { 132 | addr = uint64(f.ByteOrder().Uint32(data)) 133 | size = uint64(f.ByteOrder().Uint32(data[4:])) 134 | } else { 135 | addr = f.ByteOrder().Uint64(data) 136 | size = f.ByteOrder().Uint64(data[8:]) 137 | } 138 | if size > 1000 { 139 | return "", fmt.Errorf("implausible string size %d for runtime.buildVersion", size) 140 | } 141 | 142 | data, err = f.ReadData(addr, size) 143 | if err != nil { 144 | return "", fmt.Errorf("reading runtime.buildVersion string data: %v", err) 145 | } 146 | return string(data), nil 147 | } 148 | 149 | // Code signatures that indicate BoringCrypto or crypto/internal/fipsonly. 150 | // These are not byte literals in order to avoid the actual 151 | // byte signatures appearing in the goversion binary, 152 | // because on some systems you can't tell rodata from text. 153 | var ( 154 | sigBoringCrypto, _ = hex.DecodeString("EB1DF448F44BF4B332F52813A3B450D441CC2485F001454E92101B1D2F1950C3") 155 | sigStandardCrypto, _ = hex.DecodeString("EB1DF448F44BF4BAEE4DFA9851CA56A91145E83E99C59CF911CB8E80DAF12FC3") 156 | sigFIPSOnly, _ = hex.DecodeString("EB1DF448F44BF4363CB9CE9D68047D31F28D325D5CA5873F5D80CAF6D6151BC3") 157 | ) 158 | 159 | func findCryptoSigs(v *Version, f exe) error { 160 | const maxSigLen = 1 << 10 161 | start, end := f.TextRange() 162 | for addr := start; addr < end; { 163 | size := uint64(1 << 20) 164 | if end-addr < size { 165 | size = end - addr 166 | } 167 | data, err := f.ReadData(addr, size) 168 | if err != nil { 169 | return fmt.Errorf("reading text: %v", err) 170 | } 171 | if haveSig(data, sigBoringCrypto) { 172 | v.BoringCrypto = true 173 | } 174 | if haveSig(data, sigFIPSOnly) { 175 | v.FIPSOnly = true 176 | } 177 | if haveSig(data, sigStandardCrypto) { 178 | v.StandardCrypto = true 179 | } 180 | if addr+size < end { 181 | size -= maxSigLen 182 | } 183 | addr += size 184 | } 185 | return nil 186 | } 187 | 188 | func haveSig(data, sig []byte) bool { 189 | const align = 16 190 | for { 191 | i := bytes.Index(data, sig) 192 | if i < 0 { 193 | return false 194 | } 195 | if i&(align-1) == 0 { 196 | return true 197 | } 198 | // Found unaligned match; unexpected but 199 | // skip to next aligned boundary and keep searching. 200 | data = data[(i+align-1)&^(align-1):] 201 | } 202 | } 203 | 204 | func findModuleInfo(v *Version, f exe) error { 205 | const maxModInfo = 128 << 10 206 | start, end := f.RODataRange() 207 | for addr := start; addr < end; { 208 | size := uint64(4 << 20) 209 | if end-addr < size { 210 | size = end - addr 211 | } 212 | data, err := f.ReadData(addr, size) 213 | if err != nil { 214 | return fmt.Errorf("reading text: %v", err) 215 | } 216 | if haveModuleInfo(data, v) { 217 | return nil 218 | } 219 | if addr+size < end { 220 | size -= maxModInfo 221 | } 222 | addr += size 223 | } 224 | return nil 225 | } 226 | 227 | var ( 228 | infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") 229 | infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") 230 | ) 231 | 232 | func haveModuleInfo(data []byte, v *Version) bool { 233 | i := bytes.Index(data, infoStart) 234 | if i < 0 { 235 | return false 236 | } 237 | j := bytes.Index(data[i:], infoEnd) 238 | if j < 0 { 239 | return false 240 | } 241 | v.ModuleInfo = string(data[i+len(infoStart) : i+j]) 242 | return true 243 | } 244 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All Rights Reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Goversion scans a directory tree and, for every executable it finds, 6 | // prints the Go version used to build that executable. 7 | // 8 | // Usage: 9 | // 10 | // goversion [-crypto] [-m | -mh] [-v] path... 11 | // 12 | // The list of paths can be individual files or directories; if the latter, 13 | // goversion scans all files in the directory tree, not following symlinks. 14 | // 15 | // Goversion scans inside of tar or gzipped tar archives that it finds 16 | // (named *.tar, *.tar.gz, or *.tgz), but not recursively. 17 | // 18 | // The -crypto flag causes goversion to print additional information 19 | // about the crypto libraries linked into each executable. 20 | // 21 | // The -m flag causes goversion to print the list of modules 22 | // found in the executable, along with version information. 23 | // 24 | // The -mh flag causes goversion to print the list of modules 25 | // found in the executable, along with version and hash information. 26 | // 27 | // The -v flag causes goversion to print information about every 28 | // file it considers. 29 | // 30 | // Example 31 | // 32 | // Scan /usr/bin for Go binaries and print their versions: 33 | // 34 | // $ goversion /usr/bin 35 | // /usr/bin/containerd go1.7.4 36 | // /usr/bin/containerd-shim go1.7.4 37 | // /usr/bin/ctr go1.7.4 38 | // /usr/bin/docker go1.7.4 39 | // /usr/bin/docker-proxy go1.7.4 40 | // /usr/bin/dockerd go1.7.4 41 | // /usr/bin/kbfsfuse go1.8.3 42 | // /usr/bin/kbnm go1.8.3 43 | // /usr/bin/keybase go1.8.3 44 | // /usr/bin/snap go1.7.4 45 | // /usr/bin/snapctl go1.7.4 46 | // 47 | package main // import "rsc.io/goversion" 48 | 49 | import ( 50 | "archive/tar" 51 | "bufio" 52 | "compress/gzip" 53 | "flag" 54 | "fmt" 55 | "io" 56 | "io/ioutil" 57 | "log" 58 | "os" 59 | "path/filepath" 60 | "runtime" 61 | "strings" 62 | "unicode/utf8" 63 | 64 | "rsc.io/goversion/version" 65 | ) 66 | 67 | var ( 68 | crypto = flag.Bool("crypto", false, "check kind of crypto library") 69 | modinfo = flag.Bool("m", false, "show module info") 70 | modhash = flag.Bool("mh", false, "show module and hash info") 71 | verbose = flag.Bool("v", false, "print verbose information") 72 | ) 73 | 74 | func init() { 75 | flag.BoolVar(&version.DebugMatch, "d", version.DebugMatch, "print debug information") 76 | } 77 | 78 | func usage() { 79 | fmt.Fprintf(os.Stderr, "usage: goversion [-crypto] [-v] path...\n") 80 | flag.PrintDefaults() 81 | os.Exit(2) 82 | } 83 | 84 | func main() { 85 | log.SetFlags(0) 86 | log.SetPrefix("goversion: ") 87 | flag.Usage = usage 88 | flag.Parse() 89 | if flag.NArg() == 0 { 90 | usage() 91 | } 92 | 93 | for _, file := range flag.Args() { 94 | info, err := os.Stat(file) 95 | if err != nil { 96 | log.Print(err) 97 | continue 98 | } 99 | if info.IsDir() { 100 | scandir(file) 101 | } else { 102 | scanfile(file, file, info, true) 103 | } 104 | } 105 | } 106 | 107 | func scandir(dir string) { 108 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 109 | if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { 110 | scanfile(path, path, info, *verbose) 111 | } 112 | return nil 113 | }) 114 | } 115 | 116 | func isExe(file string, info os.FileInfo) bool { 117 | if runtime.GOOS == "windows" { 118 | return strings.HasSuffix(strings.ToLower(file), ".exe") 119 | } 120 | return info.Mode()&0111 != 0 121 | } 122 | 123 | func scanfile(file, diskFile string, info os.FileInfo, mustPrint bool) { 124 | if strings.HasSuffix(file, ".tar") { 125 | if file != diskFile { 126 | fmt.Fprintf(os.Stderr, "%s: not scanning tar recursively\n", file) 127 | return 128 | } 129 | if mustPrint { 130 | fmt.Fprintf(os.Stderr, "%s: scanning tar archive\n", file) 131 | } 132 | scantar(file, info) 133 | return 134 | } 135 | if strings.HasSuffix(file, ".tar.gz") || strings.HasSuffix(file, ".tgz") { 136 | if file != diskFile { 137 | fmt.Fprintf(os.Stderr, "%s: not scanning tgz recursively\n", file) 138 | return 139 | } 140 | if mustPrint { 141 | fmt.Fprintf(os.Stderr, "%s: scanning tgz archive\n", file) 142 | } 143 | scantar(file, info) 144 | return 145 | } 146 | if info.Mode()&os.ModeSymlink != 0 { 147 | // Accept file symlinks, but not dir symlinks, to avoid cycles. 148 | i, err := os.Stat(diskFile) 149 | if err != nil || !i.Mode().IsRegular() { 150 | if mustPrint { 151 | fmt.Fprintf(os.Stderr, "%s: symlink\n", file) 152 | } 153 | return 154 | } 155 | info = i 156 | } 157 | if file == diskFile && !isExe(file, info) { 158 | if mustPrint { 159 | fmt.Fprintf(os.Stderr, "%s: not executable\n", file) 160 | } 161 | return 162 | } 163 | v, err := version.ReadExe(diskFile) 164 | if err != nil { 165 | if mustPrint { 166 | fmt.Fprintf(os.Stderr, "%s: %v\n", file, err) 167 | } 168 | return 169 | } 170 | 171 | buildVersion := v.Release 172 | if *crypto { 173 | switch { 174 | case v.BoringCrypto && v.StandardCrypto: 175 | buildVersion += " (boring AND standard crypto!!!)" 176 | case v.BoringCrypto: 177 | buildVersion += " (boring crypto)" 178 | case v.StandardCrypto: 179 | buildVersion += " (standard crypto)" 180 | } 181 | if v.FIPSOnly { 182 | buildVersion += " +crypto/tls/fipsonly" 183 | } 184 | } 185 | fmt.Printf("%s %s\n", file, buildVersion) 186 | if (*modinfo || *modhash) && v.ModuleInfo != "" { 187 | var rows [][]string 188 | for _, line := range strings.Split(strings.TrimSpace(v.ModuleInfo), "\n") { 189 | row := strings.Split(line, "\t") 190 | if !*modhash && len(row) > 3 { 191 | row = row[:3] 192 | } 193 | rows = append(rows, row) 194 | } 195 | printTable(os.Stdout, "\t", rows) 196 | } 197 | } 198 | 199 | type Version struct { 200 | Release string 201 | BoringCrypto bool 202 | StandardCrypto bool 203 | } 204 | 205 | func scantar(file string, info os.FileInfo) { 206 | f, err := os.Open(file) 207 | if err != nil { 208 | fmt.Fprintf(os.Stderr, "%s: %v\n", file, err) 209 | return 210 | } 211 | defer f.Close() 212 | var r io.Reader = f 213 | if strings.HasSuffix(file, "z") { 214 | z, err := gzip.NewReader(r) 215 | if err != nil { 216 | fmt.Fprintf(os.Stderr, "%s: %v\n", file, err) 217 | return 218 | } 219 | defer z.Close() 220 | r = z 221 | } 222 | tr := tar.NewReader(r) 223 | for { 224 | hdr, err := tr.Next() 225 | if err != nil { 226 | break 227 | } 228 | if hdr.Typeflag != tar.TypeReg { 229 | if *verbose { 230 | fmt.Fprintf(os.Stderr, "%s/%s: not regular file\n", file, hdr.Name) 231 | } 232 | continue 233 | } 234 | if hdr.Mode&0111 == 0 { 235 | if *verbose { 236 | fmt.Fprintf(os.Stderr, "%s/%s: not executable\n", file, hdr.Name) 237 | } 238 | continue 239 | } 240 | 241 | // executable but not special 242 | tmp, err := ioutil.TempFile("", "goversion-") 243 | if err != nil { 244 | log.Fatal(err) 245 | } 246 | io.Copy(tmp, tr) 247 | tmpName := tmp.Name() 248 | info, err := tmp.Stat() 249 | if err != nil { 250 | log.Fatal(err) 251 | } 252 | tmp.Close() 253 | scanfile(file+"/"+hdr.Name, tmpName, info, *verbose) 254 | os.Remove(tmpName) 255 | } 256 | } 257 | 258 | func printTable(w io.Writer, prefix string, rows [][]string) { 259 | var max []int 260 | for _, row := range rows { 261 | for i, c := range row { 262 | n := utf8.RuneCountInString(c) 263 | if i >= len(max) { 264 | max = append(max, n) 265 | } else if max[i] < n { 266 | max[i] = n 267 | } 268 | } 269 | } 270 | 271 | b := bufio.NewWriter(w) 272 | for _, row := range rows { 273 | b.WriteString(prefix) 274 | for len(row) > 0 && row[len(row)-1] == "" { 275 | row = row[:len(row)-1] 276 | } 277 | for i, c := range row { 278 | b.WriteString(c) 279 | if i+1 < len(row) { 280 | for j := utf8.RuneCountInString(c); j < max[i]+2; j++ { 281 | b.WriteRune(' ') 282 | } 283 | } 284 | } 285 | b.WriteRune('\n') 286 | } 287 | b.Flush() 288 | } 289 | -------------------------------------------------------------------------------- /version/exe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All Rights Reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package version 6 | 7 | import ( 8 | "bytes" 9 | "debug/elf" 10 | "debug/macho" 11 | "debug/pe" 12 | "encoding/binary" 13 | "fmt" 14 | "io" 15 | "os" 16 | ) 17 | 18 | type sym struct { 19 | Name string 20 | Addr uint64 21 | Size uint64 22 | } 23 | 24 | type exe interface { 25 | AddrSize() int // bytes 26 | ReadData(addr, size uint64) ([]byte, error) 27 | Symbols() ([]sym, error) 28 | SectionNames() []string 29 | Close() error 30 | ByteOrder() binary.ByteOrder 31 | Entry() uint64 32 | TextRange() (uint64, uint64) 33 | RODataRange() (uint64, uint64) 34 | } 35 | 36 | func openExe(file string) (exe, error) { 37 | f, err := os.Open(file) 38 | if err != nil { 39 | return nil, err 40 | } 41 | data := make([]byte, 16) 42 | if _, err := io.ReadFull(f, data); err != nil { 43 | return nil, err 44 | } 45 | f.Seek(0, 0) 46 | if bytes.HasPrefix(data, []byte("\x7FELF")) { 47 | e, err := elf.NewFile(f) 48 | if err != nil { 49 | f.Close() 50 | return nil, err 51 | } 52 | return &elfExe{f, e}, nil 53 | } 54 | if bytes.HasPrefix(data, []byte("MZ")) { 55 | e, err := pe.NewFile(f) 56 | if err != nil { 57 | f.Close() 58 | return nil, err 59 | } 60 | return &peExe{f, e}, nil 61 | } 62 | if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) { 63 | e, err := macho.NewFile(f) 64 | if err != nil { 65 | f.Close() 66 | return nil, err 67 | } 68 | return &machoExe{f, e}, nil 69 | } 70 | return nil, fmt.Errorf("unrecognized executable format") 71 | } 72 | 73 | type elfExe struct { 74 | os *os.File 75 | f *elf.File 76 | } 77 | 78 | func (x *elfExe) AddrSize() int { return 0 } 79 | 80 | func (x *elfExe) ByteOrder() binary.ByteOrder { return x.f.ByteOrder } 81 | 82 | func (x *elfExe) Close() error { 83 | return x.os.Close() 84 | } 85 | 86 | func (x *elfExe) Entry() uint64 { return x.f.Entry } 87 | 88 | func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) { 89 | for _, prog := range x.f.Progs { 90 | fmt.Printf("%#x %#x %#x\n", addr, prog.Vaddr, prog.Vaddr+prog.Filesz) 91 | if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 { 92 | n := prog.Vaddr + prog.Filesz - addr 93 | if n > size { 94 | n = size 95 | } 96 | data := make([]byte, n) 97 | _, err := prog.ReadAt(data, int64(addr-prog.Vaddr)) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return data, nil 102 | } 103 | } 104 | return nil, fmt.Errorf("address not mapped") 105 | } 106 | 107 | func (x *elfExe) Symbols() ([]sym, error) { 108 | syms, err := x.f.Symbols() 109 | if err != nil { 110 | return nil, err 111 | } 112 | var out []sym 113 | for _, s := range syms { 114 | out = append(out, sym{s.Name, s.Value, s.Size}) 115 | } 116 | return out, nil 117 | } 118 | 119 | func (x *elfExe) SectionNames() []string { 120 | var names []string 121 | for _, sect := range x.f.Sections { 122 | names = append(names, sect.Name) 123 | } 124 | return names 125 | } 126 | 127 | func (x *elfExe) TextRange() (uint64, uint64) { 128 | for _, p := range x.f.Progs { 129 | if p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 { 130 | return p.Vaddr, p.Vaddr + p.Filesz 131 | } 132 | } 133 | return 0, 0 134 | } 135 | 136 | func (x *elfExe) RODataRange() (uint64, uint64) { 137 | for _, p := range x.f.Progs { 138 | if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_R|elf.PF_W|elf.PF_X) == elf.PF_R { 139 | return p.Vaddr, p.Vaddr + p.Filesz 140 | } 141 | } 142 | for _, p := range x.f.Progs { 143 | if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_R|elf.PF_W|elf.PF_X) == (elf.PF_R|elf.PF_X) { 144 | return p.Vaddr, p.Vaddr + p.Filesz 145 | } 146 | } 147 | return 0, 0 148 | } 149 | 150 | type peExe struct { 151 | os *os.File 152 | f *pe.File 153 | } 154 | 155 | func (x *peExe) imageBase() uint64 { 156 | switch oh := x.f.OptionalHeader.(type) { 157 | case *pe.OptionalHeader32: 158 | return uint64(oh.ImageBase) 159 | case *pe.OptionalHeader64: 160 | return oh.ImageBase 161 | } 162 | return 0 163 | } 164 | 165 | func (x *peExe) AddrSize() int { 166 | if x.f.Machine == pe.IMAGE_FILE_MACHINE_AMD64 { 167 | return 8 168 | } 169 | return 4 170 | } 171 | 172 | func (x *peExe) ByteOrder() binary.ByteOrder { return binary.LittleEndian } 173 | 174 | func (x *peExe) Close() error { 175 | return x.os.Close() 176 | } 177 | 178 | func (x *peExe) Entry() uint64 { 179 | switch oh := x.f.OptionalHeader.(type) { 180 | case *pe.OptionalHeader32: 181 | return uint64(oh.ImageBase + oh.AddressOfEntryPoint) 182 | case *pe.OptionalHeader64: 183 | return oh.ImageBase + uint64(oh.AddressOfEntryPoint) 184 | } 185 | return 0 186 | } 187 | 188 | func (x *peExe) ReadData(addr, size uint64) ([]byte, error) { 189 | addr -= x.imageBase() 190 | data := make([]byte, size) 191 | for _, sect := range x.f.Sections { 192 | if uint64(sect.VirtualAddress) <= addr && addr+size-1 <= uint64(sect.VirtualAddress+sect.Size-1) { 193 | _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress))) 194 | if err != nil { 195 | return nil, err 196 | } 197 | return data, nil 198 | } 199 | } 200 | return nil, fmt.Errorf("address not mapped") 201 | } 202 | 203 | func (x *peExe) Symbols() ([]sym, error) { 204 | base := x.imageBase() 205 | var out []sym 206 | for _, s := range x.f.Symbols { 207 | if s.SectionNumber <= 0 || int(s.SectionNumber) > len(x.f.Sections) { 208 | continue 209 | } 210 | sect := x.f.Sections[s.SectionNumber-1] 211 | out = append(out, sym{s.Name, uint64(s.Value) + base + uint64(sect.VirtualAddress), 0}) 212 | } 213 | return out, nil 214 | } 215 | 216 | func (x *peExe) SectionNames() []string { 217 | var names []string 218 | for _, sect := range x.f.Sections { 219 | names = append(names, sect.Name) 220 | } 221 | return names 222 | } 223 | 224 | func (x *peExe) TextRange() (uint64, uint64) { 225 | // Assume text is first non-empty section. 226 | for _, sect := range x.f.Sections { 227 | if sect.VirtualAddress != 0 && sect.Size != 0 { 228 | return uint64(sect.VirtualAddress) + x.imageBase(), uint64(sect.VirtualAddress+sect.Size) + x.imageBase() 229 | } 230 | } 231 | return 0, 0 232 | } 233 | 234 | func (x *peExe) RODataRange() (uint64, uint64) { 235 | return x.TextRange() 236 | } 237 | 238 | type machoExe struct { 239 | os *os.File 240 | f *macho.File 241 | } 242 | 243 | func (x *machoExe) AddrSize() int { 244 | if x.f.Cpu&0x01000000 != 0 { 245 | return 8 246 | } 247 | return 4 248 | } 249 | 250 | func (x *machoExe) ByteOrder() binary.ByteOrder { return x.f.ByteOrder } 251 | 252 | func (x *machoExe) Close() error { 253 | return x.os.Close() 254 | } 255 | 256 | func (x *machoExe) Entry() uint64 { 257 | for _, load := range x.f.Loads { 258 | b, ok := load.(macho.LoadBytes) 259 | if !ok { 260 | continue 261 | } 262 | // TODO: Other thread states. 263 | bo := x.f.ByteOrder 264 | const x86_THREAD_STATE64 = 4 265 | cmd, siz := macho.LoadCmd(bo.Uint32(b[0:4])), bo.Uint32(b[4:8]) 266 | if cmd == macho.LoadCmdUnixThread && siz == 184 && bo.Uint32(b[8:12]) == x86_THREAD_STATE64 { 267 | return bo.Uint64(b[144:]) 268 | } 269 | } 270 | return 0 271 | } 272 | 273 | func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) { 274 | data := make([]byte, size) 275 | for _, load := range x.f.Loads { 276 | seg, ok := load.(*macho.Segment) 277 | if !ok { 278 | continue 279 | } 280 | if seg.Addr <= addr && addr+size-1 <= seg.Addr+seg.Filesz-1 { 281 | if seg.Name == "__PAGEZERO" { 282 | continue 283 | } 284 | _, err := seg.ReadAt(data, int64(addr-seg.Addr)) 285 | if err != nil { 286 | return nil, err 287 | } 288 | return data, nil 289 | } 290 | } 291 | return nil, fmt.Errorf("address not mapped") 292 | } 293 | 294 | func (x *machoExe) Symbols() ([]sym, error) { 295 | var out []sym 296 | for _, s := range x.f.Symtab.Syms { 297 | out = append(out, sym{s.Name, s.Value, 0}) 298 | } 299 | return out, nil 300 | } 301 | 302 | func (x *machoExe) SectionNames() []string { 303 | var names []string 304 | for _, sect := range x.f.Sections { 305 | names = append(names, sect.Name) 306 | } 307 | return names 308 | } 309 | 310 | func (x *machoExe) TextRange() (uint64, uint64) { 311 | // Assume text is first non-empty segment. 312 | for _, load := range x.f.Loads { 313 | seg, ok := load.(*macho.Segment) 314 | if ok && seg.Name != "__PAGEZERO" && seg.Addr != 0 && seg.Filesz != 0 { 315 | return seg.Addr, seg.Addr + seg.Filesz 316 | } 317 | } 318 | return 0, 0 319 | } 320 | 321 | func (x *machoExe) RODataRange() (uint64, uint64) { 322 | return x.TextRange() 323 | } 324 | -------------------------------------------------------------------------------- /version/asm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All Rights Reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package version 6 | 7 | import ( 8 | "encoding/binary" 9 | "fmt" 10 | "os" 11 | ) 12 | 13 | type matcher [][]uint32 14 | 15 | const ( 16 | pWild uint32 = 0xff00 17 | pAddr uint32 = 0x10000 18 | pEnd uint32 = 0x20000 19 | pRelAddr uint32 = 0x30000 20 | 21 | opMaybe = 1 + iota 22 | opMust 23 | opDone 24 | opAnchor = 0x100 25 | opSub8 = 0x200 26 | opFlags = opAnchor | opSub8 27 | ) 28 | 29 | var amd64Matcher = matcher{ 30 | {opMaybe | opAnchor, 31 | // __rt0_amd64_darwin: 32 | // JMP __rt0_amd64 33 | 0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 0xcc, 0xcc, 0xcc, 34 | }, 35 | {opMaybe, 36 | // _rt0_amd64_linux: 37 | // lea 0x8(%rsp), %rsi 38 | // mov (%rsp), %rdi 39 | // lea ADDR(%rip), %rax # main 40 | // jmpq *%rax 41 | 0x48, 0x8d, 0x74, 0x24, 0x08, 42 | 0x48, 0x8b, 0x3c, 0x24, 0x48, 43 | 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd, 44 | 0xff, 0xe0, 45 | }, 46 | {opMaybe, 47 | // _rt0_amd64_linux: 48 | // lea 0x8(%rsp), %rsi 49 | // mov (%rsp), %rdi 50 | // mov $ADDR, %eax # main 51 | // jmpq *%rax 52 | 0x48, 0x8d, 0x74, 0x24, 0x08, 53 | 0x48, 0x8b, 0x3c, 0x24, 54 | 0xb8, pWild | pAddr, pWild, pWild, pWild, 55 | 0xff, 0xe0, 56 | }, 57 | {opMaybe, 58 | // __rt0_amd64: 59 | // mov (%rsp), %rdi 60 | // lea 8(%rsp), %rsi 61 | // jmp runtime.rt0_g0 62 | 0x48, 0x8b, 0x3c, 0x24, 63 | 0x48, 0x8d, 0x74, 0x24, 0x08, 64 | 0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 65 | 0xcc, 0xcc, 66 | }, 67 | {opMaybe, 68 | // _start (toward end) 69 | // lea __libc_csu_fini(%rip), %r8 70 | // lea __libc_csu_init(%rip), %rcx 71 | // lea ADDR(%rip), %rdi # main 72 | // callq *xxx(%rip) 73 | 0x4c, 0x8d, 0x05, pWild, pWild, pWild, pWild, 74 | 0x48, 0x8d, 0x0d, pWild, pWild, pWild, pWild, 75 | 0x48, 0x8d, 0x3d, pWild | pAddr, pWild, pWild, pWild | pEnd, 76 | 0xff, 0x15, 77 | }, 78 | {opMaybe, 79 | // _start (toward end) 80 | // push %rsp (1) 81 | // mov $__libc_csu_fini, %r8 (7) 82 | // mov $__libc_csu_init, %rcx (7) 83 | // mov $ADDR, %rdi # main (7) 84 | // callq *xxx(%rip) 85 | 0x54, 86 | 0x49, 0xc7, 0xc0, pWild, pWild, pWild, pWild, 87 | 0x48, 0xc7, 0xc1, pWild, pWild, pWild, pWild, 88 | 0x48, 0xc7, 0xc7, pAddr | pWild, pWild, pWild, pWild, 89 | }, 90 | {opMaybe | opAnchor, 91 | // main: 92 | // lea ADDR(%rip), %rax # rt0_go 93 | // jmpq *%rax 94 | 0x48, 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd, 95 | 0xff, 0xe0, 96 | }, 97 | {opMaybe | opAnchor, 98 | // main: 99 | // mov $ADDR, %eax 100 | // jmpq *%rax 101 | 0xb8, pWild | pAddr, pWild, pWild, pWild, 102 | 0xff, 0xe0, 103 | }, 104 | {opMaybe | opAnchor, 105 | // main: 106 | // JMP runtime.rt0_go(SB) 107 | 0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 0xcc, 0xcc, 0xcc, 108 | }, 109 | {opMust | opAnchor, 110 | // rt0_go: 111 | // mov %rdi, %rax 112 | // mov %rsi, %rbx 113 | // sub %0x27, %rsp 114 | // and $0xfffffffffffffff0,%rsp 115 | // mov %rax,0x10(%rsp) 116 | // mov %rbx,0x18(%rsp) 117 | 0x48, 0x89, 0xf8, 118 | 0x48, 0x89, 0xf3, 119 | 0x48, 0x83, 0xec, 0x27, 120 | 0x48, 0x83, 0xe4, 0xf0, 121 | 0x48, 0x89, 0x44, 0x24, 0x10, 122 | 0x48, 0x89, 0x5c, 0x24, 0x18, 123 | }, 124 | {opMust, 125 | // later in rt0_go: 126 | // mov %eax, (%rsp) 127 | // mov 0x18(%rsp), %rax 128 | // mov %rax, 0x8(%rsp) 129 | // callq runtime.args 130 | // callq runtime.osinit 131 | // callq runtime.schedinit (ADDR) 132 | 0x89, 0x04, 0x24, 133 | 0x48, 0x8b, 0x44, 0x24, 0x18, 134 | 0x48, 0x89, 0x44, 0x24, 0x08, 135 | 0xe8, pWild, pWild, pWild, pWild, 136 | 0xe8, pWild, pWild, pWild, pWild, 137 | 0xe8, pWild, pWild, pWild, pWild, 138 | }, 139 | {opMaybe, 140 | // later in rt0_go: 141 | // mov %eax, (%rsp) 142 | // mov 0x18(%rsp), %rax 143 | // mov %rax, 0x8(%rsp) 144 | // callq runtime.args 145 | // callq runtime.osinit 146 | // callq runtime.schedinit (ADDR) 147 | // lea other(%rip), %rdi 148 | 0x89, 0x04, 0x24, 149 | 0x48, 0x8b, 0x44, 0x24, 0x18, 150 | 0x48, 0x89, 0x44, 0x24, 0x08, 151 | 0xe8, pWild, pWild, pWild, pWild, 152 | 0xe8, pWild, pWild, pWild, pWild, 153 | 0xe8, pWild | pAddr, pWild, pWild, pWild | pEnd, 154 | 0x48, 0x8d, 0x05, 155 | }, 156 | {opMaybe, 157 | // later in rt0_go: 158 | // mov %eax, (%rsp) 159 | // mov 0x18(%rsp), %rax 160 | // mov %rax, 0x8(%rsp) 161 | // callq runtime.args 162 | // callq runtime.osinit 163 | // callq runtime.hashinit 164 | // callq runtime.schedinit (ADDR) 165 | // pushq $main.main 166 | 0x89, 0x04, 0x24, 167 | 0x48, 0x8b, 0x44, 0x24, 0x18, 168 | 0x48, 0x89, 0x44, 0x24, 0x08, 169 | 0xe8, pWild, pWild, pWild, pWild, 170 | 0xe8, pWild, pWild, pWild, pWild, 171 | 0xe8, pWild, pWild, pWild, pWild, 172 | 0xe8, pWild | pAddr, pWild, pWild, pWild | pEnd, 173 | 0x68, 174 | }, 175 | {opDone | opSub8, 176 | // schedinit (toward end) 177 | // mov ADDR(%rip), %rax 178 | // test %rax, %rax 179 | // jne 180 | // movq $0x7, ADDR(%rip) 181 | // 182 | 0x48, 0x8b, 0x05, pWild, pWild, pWild, pWild, 183 | 0x48, 0x85, 0xc0, 184 | 0x75, pWild, 185 | 0x48, 0xc7, 0x05, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00 | pEnd, 186 | }, 187 | {opDone | opSub8, 188 | // schedinit (toward end) 189 | // mov ADDR(%rip), %rbx 190 | // cmp $0x0, %rbx 191 | // jne 192 | // lea "unknown"(%rip), %rbx 193 | // mov %rbx, ADDR(%rip) 194 | // movq $7, (ADDR+8)(%rip) 195 | 0x48, 0x8b, 0x1d, pWild, pWild, pWild, pWild, 196 | 0x48, 0x83, 0xfb, 0x00, 197 | 0x75, pWild, 198 | 0x48, 0x8d, 0x1d, pWild, pWild, pWild, pWild, 199 | 0x48, 0x89, 0x1d, pWild, pWild, pWild, pWild, 200 | 0x48, 0xc7, 0x05, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00 | pEnd, 201 | }, 202 | {opDone, 203 | // schedinit (toward end) 204 | // cmpq $0x0, ADDR(%rip) 205 | // jne 206 | // lea "unknown"(%rip), %rax 207 | // mov %rax, ADDR(%rip) 208 | // lea ADDR(%rip), %rax 209 | // movq $7, 8(%rax) 210 | 0x48, 0x83, 0x3d, pWild | pAddr, pWild, pWild, pWild, 0x00, 211 | 0x75, pWild, 212 | 0x48, 0x8d, 0x05, pWild, pWild, pWild, pWild, 213 | 0x48, 0x89, 0x05, pWild, pWild, pWild, pWild, 214 | 0x48, 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd, 215 | 0x48, 0xc7, 0x40, 0x08, 0x07, 0x00, 0x00, 0x00, 216 | }, 217 | {opDone, 218 | // schedinit (toward end) 219 | // cmpq $0x0, ADDR(%rip) 220 | // jne 221 | // movq $0x7, ADDR(%rip) 222 | 0x48, 0x83, 0x3d, pWild | pAddr, pWild, pWild, pWild, 0x00, 223 | 0x75, pWild, 224 | 0x48, 0xc7, 0x05 | pEnd, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00, 225 | }, 226 | {opDone, 227 | // test %eax, %eax 228 | // jne 229 | // lea "unknown"(RIP), %rax 230 | // mov %rax, ADDR(%rip) 231 | 0x48, 0x85, 0xc0, 0x75, pWild, 0x48, 0x8d, 0x05, pWild, pWild, pWild, pWild, 0x48, 0x89, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd, 232 | }, 233 | {opDone, 234 | // schedinit (toward end) 235 | // mov ADDR(%rip), %rcx 236 | // test %rcx, %rcx 237 | // jne 238 | // movq $0x7, ADDR(%rip) 239 | // 240 | 0x48, 0x8b, 0x0d, pWild, pWild, pWild, pWild, 241 | 0x48, 0x85, 0xc9, 242 | 0x75, pWild, 243 | 0x48, 0xc7, 0x05 | pEnd, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00, 244 | }, 245 | } 246 | 247 | var DebugMatch bool 248 | 249 | func (m matcher) match(f exe, addr uint64) (uint64, bool) { 250 | data, err := f.ReadData(addr, 512) 251 | if DebugMatch { 252 | fmt.Fprintf(os.Stderr, "data @%#x: %x\n", addr, data[:16]) 253 | } 254 | if err != nil { 255 | if DebugMatch { 256 | fmt.Fprintf(os.Stderr, "match: %v\n", err) 257 | } 258 | return 0, false 259 | } 260 | if DebugMatch { 261 | fmt.Fprintf(os.Stderr, "data: %x\n", data[:32]) 262 | } 263 | Matchers: 264 | for pc, p := range m { 265 | op := p[0] 266 | p = p[1:] 267 | Search: 268 | for i := 0; i <= len(data)-len(p); i++ { 269 | a := -1 270 | e := -1 271 | if i > 0 && op&opAnchor != 0 { 272 | break 273 | } 274 | for j := 0; j < len(p); j++ { 275 | b := byte(p[j]) 276 | m := byte(p[j] >> 8) 277 | if data[i+j]&^m != b { 278 | continue Search 279 | } 280 | if p[j]&pAddr != 0 { 281 | a = j 282 | } 283 | if p[j]&pEnd != 0 { 284 | e = j + 1 285 | } 286 | } 287 | // matched 288 | if DebugMatch { 289 | fmt.Fprintf(os.Stderr, "match (%d) %#x+%d %x %x\n", pc, addr, i, p, data[i:i+len(p)]) 290 | } 291 | if a != -1 { 292 | val := uint64(int32(binary.LittleEndian.Uint32(data[i+a:]))) 293 | if e == -1 { 294 | addr = val 295 | } else { 296 | addr += uint64(i+e) + val 297 | } 298 | if op&opSub8 != 0 { 299 | addr -= 8 300 | } 301 | } 302 | if op&^opFlags == opDone { 303 | if DebugMatch { 304 | fmt.Fprintf(os.Stderr, "done %x\n", addr) 305 | } 306 | return addr, true 307 | } 308 | if a != -1 { 309 | // changed addr, so reload 310 | data, err = f.ReadData(addr, 512) 311 | if err != nil { 312 | return 0, false 313 | } 314 | if DebugMatch { 315 | fmt.Fprintf(os.Stderr, "reload @%#x: %x\n", addr, data[:32]) 316 | } 317 | } 318 | continue Matchers 319 | } 320 | // not matched 321 | if DebugMatch { 322 | fmt.Fprintf(os.Stderr, "no match (%d) %#x %x %x\n", pc, addr, p, data[:32]) 323 | } 324 | if op&^opFlags == opMust { 325 | return 0, false 326 | } 327 | } 328 | // ran off end of matcher 329 | return 0, false 330 | } 331 | 332 | func readBuildVersionX86Asm(f exe) (isGo bool, buildVersion string) { 333 | entry := f.Entry() 334 | if entry == 0 { 335 | if DebugMatch { 336 | fmt.Fprintf(os.Stderr, "missing entry!\n") 337 | } 338 | return 339 | } 340 | addr, ok := amd64Matcher.match(f, entry) 341 | if !ok { 342 | return 343 | } 344 | v, err := readBuildVersion(f, addr, 16) 345 | if err != nil { 346 | return 347 | } 348 | return true, v 349 | } 350 | --------------------------------------------------------------------------------