delve/pkg/proc/disasm_amd64.go
aarzilli 5155ef047f proc,dwarf/line: support is_stmt and prologue_end flags
Go1.11 uses the is_stmt flag of .debug_line to communicate which
assembly instructions are good places for breakpoints, we should
respect this flag.

These changes were introduced by:
* https://go-review.googlesource.com/c/go/+/102435/

Additionally when setting next breakpoints ignore all PC addresses that
belong to the same line as the one currently under at the cursor. This
matches the behavior of gdb and avoids stopping multiple times at the
heading line of a for statement with go1.11.

Change: https://go-review.googlesource.com/c/go/+/110416 adds the
prologue_end flag to the .debug_line section to communicate the end of
the stack-split prologue. We should use it instead of pattern matching
the disassembly when available.

Fixes #550

type of interfaces
'c7cde8b'.
2018-06-11 11:09:02 -07:00

184 lines
4.6 KiB
Go

package proc
import (
"encoding/binary"
"golang.org/x/arch/x86/x86asm"
)
var maxInstructionLength uint64 = 15
type ArchInst x86asm.Inst
func asmDecode(mem []byte, pc uint64) (*ArchInst, error) {
inst, err := x86asm.Decode(mem, 64)
if err != nil {
return nil, err
}
patchPCRel(pc, &inst)
r := ArchInst(inst)
return &r, nil
}
func (inst *ArchInst) Size() int {
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))
}
}
}
func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
if inst.Inst == nil {
return "?"
}
var text string
switch flavour {
case GNUFlavour:
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)
case IntelFlavour:
fallthrough
default:
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
}
return text
}
func (inst *AsmInstruction) IsCall() bool {
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
}
func resolveCallArg(inst *ArchInst, 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 nil
}
return &Location{PC: pc, File: file, Line: line, Fn: fn}
}
type instrseq []x86asm.Op
// 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)
}
}
}
// firstPCAfterPrologueDisassembly returns the address of the first
// instruction after the prologue for function fn by disassembling fn and
// matching the instructions against known split-stack prologue patterns.
// If sameline is set firstPCAfterPrologueDisassembly will always return an
// address associated with the same line as fn.Entry
func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (uint64, error) {
var mem MemoryReadWriter = p.CurrentThread()
breakpoints := p.Breakpoints()
bi := p.BinInfo()
text, err := disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End, false)
if err != nil {
return fn.Entry, err
}
if len(text) <= 0 {
return fn.Entry, nil
}
for _, prologue := range prologues {
if len(prologue) >= len(text) {
continue
}
if checkPrologue(text, prologue) {
r := &text[len(prologue)]
if sameline {
if r.Loc.Line != text[0].Loc.Line {
return fn.Entry, nil
}
}
return r.Loc.PC, nil
}
}
return fn.Entry, nil
}
func checkPrologue(s []AsmInstruction, prologuePattern instrseq) bool {
line := s[0].Loc.Line
for i, op := range prologuePattern {
if s[i].Inst.Op != op || s[i].Loc.Line != line {
return false
}
}
return true
}