160 lines
4.4 KiB
Go
160 lines
4.4 KiB
Go
![]() |
// 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.
|
||
|
|
||
|
package proc
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
|
||
|
"golang.org/x/arch/x86/x86asm"
|
||
|
)
|
||
|
|
||
|
// AsmDecode decodes the assembly instruction starting at mem[0:] into asmInst.
|
||
|
// It assumes that the Loc and AtPC fields of asmInst have already been filled.
|
||
|
func (a *AMD64) AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error {
|
||
|
inst, err := x86asm.Decode(mem, 64)
|
||
|
if err != nil {
|
||
|
asmInst.Inst = (*amd64ArchInst)(nil)
|
||
|
asmInst.Size = 1
|
||
|
asmInst.Bytes = mem[:asmInst.Size]
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
asmInst.Size = inst.Len
|
||
|
asmInst.Bytes = mem[:asmInst.Size]
|
||
|
patchPCRelAMD64(asmInst.Loc.PC, &inst)
|
||
|
asmInst.Inst = (*amd64ArchInst)(&inst)
|
||
|
asmInst.Kind = OtherInstruction
|
||
|
|
||
|
switch inst.Op {
|
||
|
case x86asm.CALL, x86asm.LCALL:
|
||
|
asmInst.Kind = CallInstruction
|
||
|
case x86asm.RET, x86asm.LRET:
|
||
|
asmInst.Kind = RetInstruction
|
||
|
}
|
||
|
|
||
|
asmInst.DestLoc = resolveCallArgAMD64(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (a *AMD64) Prologues() []opcodeSeq {
|
||
|
return prologuesAMD64
|
||
|
}
|
||
|
|
||
|
// converts PC relative arguments to absolute addresses
|
||
|
func patchPCRelAMD64(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))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type amd64ArchInst x86asm.Inst
|
||
|
|
||
|
func (inst *amd64ArchInst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
|
||
|
if inst == nil {
|
||
|
return "?"
|
||
|
}
|
||
|
|
||
|
var text string
|
||
|
|
||
|
switch flavour {
|
||
|
case GNUFlavour:
|
||
|
text = x86asm.GNUSyntax(x86asm.Inst(*inst), pc, symLookup)
|
||
|
case GoFlavour:
|
||
|
text = x86asm.GoSyntax(x86asm.Inst(*inst), pc, symLookup)
|
||
|
case IntelFlavour:
|
||
|
fallthrough
|
||
|
default:
|
||
|
text = x86asm.IntelSyntax(x86asm.Inst(*inst), pc, symLookup)
|
||
|
}
|
||
|
|
||
|
return text
|
||
|
|
||
|
}
|
||
|
|
||
|
func (inst *amd64ArchInst) OpcodeEquals(op uint64) bool {
|
||
|
if inst == nil {
|
||
|
return false
|
||
|
}
|
||
|
return uint64(inst.Op) == op
|
||
|
}
|
||
|
|
||
|
func resolveCallArgAMD64(inst *x86asm.Inst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
|
||
|
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?
|
||
|
pcbytes := make([]byte, inst.MemBytes)
|
||
|
_, err := mem.ReadMemory(pcbytes, addr)
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
pc = binary.LittleEndian.Uint64(pcbytes)
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
file, line, fn := bininfo.PCToLine(pc)
|
||
|
if fn == nil {
|
||
|
return &Location{PC: pc}
|
||
|
}
|
||
|
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
||
|
}
|
||
|
|
||
|
// 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 prologuesAMD64 []opcodeSeq
|
||
|
|
||
|
func init() {
|
||
|
var tinyStacksplit = opcodeSeq{uint64(x86asm.CMP), uint64(x86asm.JBE)}
|
||
|
var smallStacksplit = opcodeSeq{uint64(x86asm.LEA), uint64(x86asm.CMP), uint64(x86asm.JBE)}
|
||
|
var bigStacksplit = opcodeSeq{uint64(x86asm.MOV), uint64(x86asm.CMP), uint64(x86asm.JE), uint64(x86asm.LEA), uint64(x86asm.SUB), uint64(x86asm.CMP), uint64(x86asm.JBE)}
|
||
|
var unixGetG = opcodeSeq{uint64(x86asm.MOV)}
|
||
|
var windowsGetG = opcodeSeq{uint64(x86asm.MOV), uint64(x86asm.MOV)}
|
||
|
|
||
|
prologuesAMD64 = make([]opcodeSeq, 0, 2*3)
|
||
|
for _, getG := range []opcodeSeq{unixGetG, windowsGetG} {
|
||
|
for _, stacksplit := range []opcodeSeq{tinyStacksplit, smallStacksplit, bigStacksplit} {
|
||
|
prologue := make(opcodeSeq, 0, len(getG)+len(stacksplit))
|
||
|
prologue = append(prologue, getG...)
|
||
|
prologue = append(prologue, stacksplit...)
|
||
|
prologuesAMD64 = append(prologuesAMD64, prologue)
|
||
|
}
|
||
|
}
|
||
|
}
|