blob: 1c165e60d3382eaad3fd4d80b87e44726b9a1b05 [file] [log] [blame]
// Copyright 2017 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 goversion
import (
"encoding/binary"
"fmt"
"os"
)
type matcher [][]uint32
const (
pWild uint32 = 0xff00
pAddr uint32 = 0x10000
pEnd uint32 = 0x20000
pRelAddr uint32 = 0x30000
opMaybe = 1 + iota
opMust
opDone
opAnchor = 0x100
opSub8 = 0x200
opFlags = opAnchor | opSub8
)
var amd64Matcher = matcher{
{opMaybe | opAnchor,
// __rt0_amd64_darwin:
// JMP __rt0_amd64
0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 0xcc, 0xcc, 0xcc,
},
{opMaybe,
// _rt0_amd64_linux:
// lea 0x8(%rsp), %rsi
// mov (%rsp), %rdi
// lea ADDR(%rip), %rax # main
// jmpq *%rax
0x48, 0x8d, 0x74, 0x24, 0x08,
0x48, 0x8b, 0x3c, 0x24, 0x48,
0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
0xff, 0xe0,
},
{opMaybe,
// _rt0_amd64_linux:
// lea 0x8(%rsp), %rsi
// mov (%rsp), %rdi
// mov $ADDR, %eax # main
// jmpq *%rax
0x48, 0x8d, 0x74, 0x24, 0x08,
0x48, 0x8b, 0x3c, 0x24,
0xb8, pWild | pAddr, pWild, pWild, pWild,
0xff, 0xe0,
},
{opMaybe,
// __rt0_amd64:
// mov (%rsp), %rdi
// lea 8(%rsp), %rsi
// jmp runtime.rt0_g0
0x48, 0x8b, 0x3c, 0x24,
0x48, 0x8d, 0x74, 0x24, 0x08,
0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd,
0xcc, 0xcc,
},
{opMaybe,
// _start (toward end)
// lea __libc_csu_fini(%rip), %r8
// lea __libc_csu_init(%rip), %rcx
// lea ADDR(%rip), %rdi # main
// callq *xxx(%rip)
0x4c, 0x8d, 0x05, pWild, pWild, pWild, pWild,
0x48, 0x8d, 0x0d, pWild, pWild, pWild, pWild,
0x48, 0x8d, 0x3d, pWild | pAddr, pWild, pWild, pWild | pEnd,
0xff, 0x15,
},
{opMaybe,
// _start (toward end)
// push %rsp (1)
// mov $__libc_csu_fini, %r8 (7)
// mov $__libc_csu_init, %rcx (7)
// mov $ADDR, %rdi # main (7)
// callq *xxx(%rip)
0x54,
0x49, 0xc7, 0xc0, pWild, pWild, pWild, pWild,
0x48, 0xc7, 0xc1, pWild, pWild, pWild, pWild,
0x48, 0xc7, 0xc7, pAddr | pWild, pWild, pWild, pWild,
},
{opMaybe | opAnchor,
// main:
// lea ADDR(%rip), %rax # rt0_go
// jmpq *%rax
0x48, 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
0xff, 0xe0,
},
{opMaybe | opAnchor,
// main:
// mov $ADDR, %eax
// jmpq *%rax
0xb8, pWild | pAddr, pWild, pWild, pWild,
0xff, 0xe0,
},
{opMaybe | opAnchor,
// main:
// JMP runtime.rt0_go(SB)
0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 0xcc, 0xcc, 0xcc,
},
{opMust | opAnchor,
// rt0_go:
// mov %rdi, %rax
// mov %rsi, %rbx
// sub %0x27, %rsp
// and $0xfffffffffffffff0,%rsp
// mov %rax,0x10(%rsp)
// mov %rbx,0x18(%rsp)
0x48, 0x89, 0xf8,
0x48, 0x89, 0xf3,
0x48, 0x83, 0xec, 0x27,
0x48, 0x83, 0xe4, 0xf0,
0x48, 0x89, 0x44, 0x24, 0x10,
0x48, 0x89, 0x5c, 0x24, 0x18,
},
{opMust,
// later in rt0_go:
// mov %eax, (%rsp)
// mov 0x18(%rsp), %rax
// mov %rax, 0x8(%rsp)
// callq runtime.args
// callq runtime.osinit
// callq runtime.schedinit (ADDR)
0x89, 0x04, 0x24,
0x48, 0x8b, 0x44, 0x24, 0x18,
0x48, 0x89, 0x44, 0x24, 0x08,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild, pWild, pWild, pWild,
},
{opMaybe,
// later in rt0_go:
// mov %eax, (%rsp)
// mov 0x18(%rsp), %rax
// mov %rax, 0x8(%rsp)
// callq runtime.args
// callq runtime.osinit
// callq runtime.schedinit (ADDR)
// lea other(%rip), %rdi
0x89, 0x04, 0x24,
0x48, 0x8b, 0x44, 0x24, 0x18,
0x48, 0x89, 0x44, 0x24, 0x08,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild | pAddr, pWild, pWild, pWild | pEnd,
0x48, 0x8d, 0x05,
},
{opMaybe,
// later in rt0_go:
// mov %eax, (%rsp)
// mov 0x18(%rsp), %rax
// mov %rax, 0x8(%rsp)
// callq runtime.args
// callq runtime.osinit
// callq runtime.hashinit
// callq runtime.schedinit (ADDR)
// pushq $main.main
0x89, 0x04, 0x24,
0x48, 0x8b, 0x44, 0x24, 0x18,
0x48, 0x89, 0x44, 0x24, 0x08,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild, pWild, pWild, pWild,
0xe8, pWild | pAddr, pWild, pWild, pWild | pEnd,
0x68,
},
{opDone | opSub8,
// schedinit (toward end)
// mov ADDR(%rip), %rax
// test %rax, %rax
// jne <short>
// movq $0x7, ADDR(%rip)
//
0x48, 0x8b, 0x05, pWild, pWild, pWild, pWild,
0x48, 0x85, 0xc0,
0x75, pWild,
0x48, 0xc7, 0x05, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00 | pEnd,
},
{opDone | opSub8,
// schedinit (toward end)
// mov ADDR(%rip), %rbx
// cmp $0x0, %rbx
// jne <short>
// lea "unknown"(%rip), %rbx
// mov %rbx, ADDR(%rip)
// movq $7, (ADDR+8)(%rip)
0x48, 0x8b, 0x1d, pWild, pWild, pWild, pWild,
0x48, 0x83, 0xfb, 0x00,
0x75, pWild,
0x48, 0x8d, 0x1d, pWild, pWild, pWild, pWild,
0x48, 0x89, 0x1d, pWild, pWild, pWild, pWild,
0x48, 0xc7, 0x05, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00 | pEnd,
},
{opDone,
// schedinit (toward end)
// cmpq $0x0, ADDR(%rip)
// jne <short>
// lea "unknown"(%rip), %rax
// mov %rax, ADDR(%rip)
// lea ADDR(%rip), %rax
// movq $7, 8(%rax)
0x48, 0x83, 0x3d, pWild | pAddr, pWild, pWild, pWild, 0x00,
0x75, pWild,
0x48, 0x8d, 0x05, pWild, pWild, pWild, pWild,
0x48, 0x89, 0x05, pWild, pWild, pWild, pWild,
0x48, 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
0x48, 0xc7, 0x40, 0x08, 0x07, 0x00, 0x00, 0x00,
},
{opDone,
// schedinit (toward end)
// cmpq $0x0, ADDR(%rip)
// jne <short>
// movq $0x7, ADDR(%rip)
0x48, 0x83, 0x3d, pWild | pAddr, pWild, pWild, pWild, 0x00,
0x75, pWild,
0x48, 0xc7, 0x05 | pEnd, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00,
},
{opDone,
// test %eax, %eax
// jne <later>
// lea "unknown"(RIP), %rax
// mov %rax, ADDR(%rip)
0x48, 0x85, 0xc0, 0x75, pWild, 0x48, 0x8d, 0x05, pWild, pWild, pWild, pWild, 0x48, 0x89, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
},
{opDone,
// schedinit (toward end)
// mov ADDR(%rip), %rcx
// test %rcx, %rcx
// jne <short>
// movq $0x7, ADDR(%rip)
//
0x48, 0x8b, 0x0d, pWild, pWild, pWild, pWild,
0x48, 0x85, 0xc9,
0x75, pWild,
0x48, 0xc7, 0x05 | pEnd, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00,
},
}
var DebugMatch bool
func (m matcher) match(f exe, addr uint64) (uint64, bool) {
data, err := f.ReadData(addr, 512)
if DebugMatch {
fmt.Fprintf(os.Stderr, "data @%#x: %x\n", addr, data[:16])
}
if err != nil {
if DebugMatch {
fmt.Fprintf(os.Stderr, "match: %v\n", err)
}
return 0, false
}
if DebugMatch {
fmt.Fprintf(os.Stderr, "data: %x\n", data[:32])
}
Matchers:
for pc, p := range m {
op := p[0]
p = p[1:]
Search:
for i := 0; i <= len(data)-len(p); i++ {
a := -1
e := -1
if i > 0 && op&opAnchor != 0 {
break
}
for j := 0; j < len(p); j++ {
b := byte(p[j])
m := byte(p[j] >> 8)
if data[i+j]&^m != b {
continue Search
}
if p[j]&pAddr != 0 {
a = j
}
if p[j]&pEnd != 0 {
e = j + 1
}
}
// matched
if DebugMatch {
fmt.Fprintf(os.Stderr, "match (%d) %#x+%d %x %x\n", pc, addr, i, p, data[i:i+len(p)])
}
if a != -1 {
val := uint64(int32(binary.LittleEndian.Uint32(data[i+a:])))
if e == -1 {
addr = val
} else {
addr += uint64(i+e) + val
}
if op&opSub8 != 0 {
addr -= 8
}
}
if op&^opFlags == opDone {
if DebugMatch {
fmt.Fprintf(os.Stderr, "done %x\n", addr)
}
return addr, true
}
if a != -1 {
// changed addr, so reload
data, err = f.ReadData(addr, 512)
if err != nil {
return 0, false
}
if DebugMatch {
fmt.Fprintf(os.Stderr, "reload @%#x: %x\n", addr, data[:32])
}
}
continue Matchers
}
// not matched
if DebugMatch {
fmt.Fprintf(os.Stderr, "no match (%d) %#x %x %x\n", pc, addr, p, data[:32])
}
if op&^opFlags == opMust {
return 0, false
}
}
// ran off end of matcher
return 0, false
}
func readBuildVersionX86Asm(f exe) (isGo bool, buildVersion string) {
entry := f.Entry()
if entry == 0 {
if DebugMatch {
fmt.Fprintf(os.Stderr, "missing entry!\n")
}
return
}
addr, ok := amd64Matcher.match(f, entry)
if !ok {
return
}
v, err := readBuildVersion(f, addr, 16)
if err != nil {
return
}
return true, v
}