// 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 // 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 // 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 // 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 // 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 // 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 // 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 }