disasm_amd64.go 4.0 KB
Newer Older
1 2 3 4
// TODO: disassembler support should be compiled in unconditionally,
// instead of being decided by the build-target architecture, and be
// part of the Arch object instead.

A
aarzilli 已提交
5 6 7 8
package proc

import (
	"encoding/binary"
D
Derek Parker 已提交
9

10
	"golang.org/x/arch/x86/x86asm"
A
aarzilli 已提交
11 12
)

13 14
var maxInstructionLength uint64 = 15

15
type archInst x86asm.Inst
A
aarzilli 已提交
16

17
func asmDecode(mem []byte, pc uint64) (*archInst, error) {
A
aarzilli 已提交
18 19 20 21 22
	inst, err := x86asm.Decode(mem, 64)
	if err != nil {
		return nil, err
	}
	patchPCRel(pc, &inst)
23
	r := archInst(inst)
A
aarzilli 已提交
24 25 26
	return &r, nil
}

27
func (inst *archInst) Size() int {
A
aarzilli 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40
	return inst.Len
}

// converts PC relative arguments to absolute addresses
func patchPCRel(pc uint64, inst *x86asm.Inst) {
	for i := range inst.Args {
		rel, isrel := inst.Args[i].(x86asm.Rel)
		if isrel {
			inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len))
		}
	}
}

41 42
// Text will return the assembly instructions in human readable format according to
// the flavour specified.
43
func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
A
aarzilli 已提交
44 45 46 47 48 49 50 51
	if inst.Inst == nil {
		return "?"
	}

	var text string

	switch flavour {
	case GNUFlavour:
52 53 54
		text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
	case GoFlavour:
		text = x86asm.GoSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
A
aarzilli 已提交
55 56 57
	case IntelFlavour:
		fallthrough
	default:
58
		text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
A
aarzilli 已提交
59 60 61 62 63
	}

	return text
}

64
// IsCall returns true if the instruction is a CALL or LCALL instruction.
A
aarzilli 已提交
65
func (inst *AsmInstruction) IsCall() bool {
A
aarzilli 已提交
66 67 68
	if inst.Inst == nil {
		return false
	}
A
aarzilli 已提交
69 70 71
	return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
}

D
Derek Parker 已提交
72 73 74 75 76 77 78 79
// IsRet returns true if the instruction is a RET or LRET instruction.
func (inst *AsmInstruction) IsRet() bool {
	if inst.Inst == nil {
		return false
	}
	return inst.Inst.Op == x86asm.RET || inst.Inst.Op == x86asm.LRET
}

80
func resolveCallArg(inst *archInst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
A
aarzilli 已提交
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
	if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
		return nil
	}

	var pc uint64
	var err error

	switch arg := inst.Args[0].(type) {
	case x86asm.Imm:
		pc = uint64(arg)
	case x86asm.Reg:
		if !currentGoroutine || regs == nil {
			return nil
		}
		pc, err = regs.Get(int(arg))
		if err != nil {
			return nil
		}
	case x86asm.Mem:
		if !currentGoroutine || regs == nil {
			return nil
		}
		if arg.Segment != 0 {
			return nil
		}
		base, err1 := regs.Get(int(arg.Base))
		index, err2 := regs.Get(int(arg.Index))
		if err1 != nil || err2 != nil {
			return nil
		}
		addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
		//TODO: should this always be 64 bits instead of inst.MemBytes?
113 114
		pcbytes := make([]byte, inst.MemBytes)
		_, err := mem.ReadMemory(pcbytes, addr)
A
aarzilli 已提交
115 116 117 118 119 120 121 122
		if err != nil {
			return nil
		}
		pc = binary.LittleEndian.Uint64(pcbytes)
	default:
		return nil
	}

123
	file, line, fn := bininfo.PCToLine(pc)
A
aarzilli 已提交
124
	if fn == nil {
125
		return &Location{PC: pc}
A
aarzilli 已提交
126 127 128
	}
	return &Location{PC: pc, File: file, Line: line, Fn: fn}
}
129 130 131

type instrseq []x86asm.Op

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
// Possible stacksplit prologues are inserted by stacksplit in
// $GOROOT/src/cmd/internal/obj/x86/obj6.go.
// The stacksplit prologue will always begin with loading curg in CX, this
// instruction is added by load_g_cx in the same file and is either 1 or 2
// MOVs.
var prologues []instrseq

func init() {
	var tinyStacksplit = instrseq{x86asm.CMP, x86asm.JBE}
	var smallStacksplit = instrseq{x86asm.LEA, x86asm.CMP, x86asm.JBE}
	var bigStacksplit = instrseq{x86asm.MOV, x86asm.CMP, x86asm.JE, x86asm.LEA, x86asm.SUB, x86asm.CMP, x86asm.JBE}
	var unixGetG = instrseq{x86asm.MOV}
	var windowsGetG = instrseq{x86asm.MOV, x86asm.MOV}

	prologues = make([]instrseq, 0, 2*3)
	for _, getG := range []instrseq{unixGetG, windowsGetG} {
		for _, stacksplit := range []instrseq{tinyStacksplit, smallStacksplit, bigStacksplit} {
			prologue := make(instrseq, 0, len(getG)+len(stacksplit))
			prologue = append(prologue, getG...)
			prologue = append(prologue, stacksplit...)
			prologues = append(prologues, prologue)
		}
	}
}