
Instead of repeatedly calling StepInstruction set breakpoints to the destination of CALL instructions (or on the CALL instructions themselves for indirect CALLs), then call Continue. Calls to unexported runtime functions are skipped. Reduces the number of code paths managing inferior state from 3 to 2 (StepInstruction, Continue). Fixes #561
206 lines
6.3 KiB
Go
206 lines
6.3 KiB
Go
package proc
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/constant"
|
|
"reflect"
|
|
)
|
|
|
|
// Breakpoint represents a breakpoint. Stores information on the break
|
|
// point including the byte of data that originally was stored at that
|
|
// address.
|
|
type Breakpoint struct {
|
|
// File & line information for printing.
|
|
FunctionName string
|
|
File string
|
|
Line int
|
|
|
|
Addr uint64 // Address breakpoint is set for.
|
|
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
|
|
Name string // User defined name of the breakpoint
|
|
ID int // Monotonically increasing ID.
|
|
Kind BreakpointKind // Whether this is a temp breakpoint (for next'ing or stepping).
|
|
|
|
// Breakpoint information
|
|
Tracepoint bool // Tracepoint flag
|
|
Goroutine bool // Retrieve goroutine information
|
|
Stacktrace int // Number of stack frames to retrieve
|
|
Variables []string // Variables to evaluate
|
|
LoadArgs *LoadConfig
|
|
LoadLocals *LoadConfig
|
|
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
|
|
TotalHitCount uint64 // Number of times a breakpoint has been reached
|
|
|
|
// DeferReturns: when kind == NextDeferBreakpoint this breakpoint
|
|
// will also check if the caller is runtime.gopanic or if the return
|
|
// address is in the DeferReturns array.
|
|
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
|
|
// deferred function, DeferReturns is populated with the
|
|
// addresses of calls to runtime.deferreturn in the current
|
|
// function. This insures that the breakpoint on the deferred
|
|
// function only triggers on panic or on the defer call to
|
|
// the function, not when the function is called directly
|
|
DeferReturns []uint64
|
|
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
|
|
Cond ast.Expr
|
|
}
|
|
|
|
// Breakpoint Kind determines the behavior of delve when the
|
|
// breakpoint is reached.
|
|
type BreakpointKind int
|
|
|
|
const (
|
|
// UserBreakpoint is a user set breakpoint
|
|
UserBreakpoint BreakpointKind = iota
|
|
// NextBreakpoint is a breakpoint set by Next, Continue
|
|
// will stop on it and delete it
|
|
NextBreakpoint
|
|
// NextDeferBreakpoint is a breakpoint set by Next on the
|
|
// first deferred function. In addition to checking their condition
|
|
// breakpoints of this kind will also check that the function has been
|
|
// called by runtime.gopanic or through runtime.deferreturn.
|
|
NextDeferBreakpoint
|
|
// StepBreakpoint is a breakpoint set by Step on a CALL instruction,
|
|
// Continue will set a new breakpoint (of NextBreakpoint kind) on the
|
|
// destination of CALL, delete this breakpoint and then continue again
|
|
StepBreakpoint
|
|
)
|
|
|
|
func (bp *Breakpoint) String() string {
|
|
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.ID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
|
|
}
|
|
|
|
// Clear this breakpoint appropriately depending on whether it is a
|
|
// hardware or software breakpoint.
|
|
func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
|
|
if _, err := thread.writeMemory(uintptr(bp.Addr), bp.OriginalData); err != nil {
|
|
return nil, fmt.Errorf("could not clear breakpoint %s", err)
|
|
}
|
|
return bp, nil
|
|
}
|
|
|
|
// BreakpointExistsError is returned when trying to set a breakpoint at
|
|
// an address that already has a breakpoint set for it.
|
|
type BreakpointExistsError struct {
|
|
file string
|
|
line int
|
|
addr uint64
|
|
}
|
|
|
|
func (bpe BreakpointExistsError) Error() string {
|
|
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
|
}
|
|
|
|
// InvalidAddressError represents the result of
|
|
// attempting to set a breakpoint at an invalid address.
|
|
type InvalidAddressError struct {
|
|
address uint64
|
|
}
|
|
|
|
func (iae InvalidAddressError) Error() string {
|
|
return fmt.Sprintf("Invalid address %#v\n", iae.address)
|
|
}
|
|
|
|
func (dbp *Process) setBreakpoint(tid int, addr uint64, kind BreakpointKind) (*Breakpoint, error) {
|
|
if bp, ok := dbp.FindBreakpoint(addr); ok {
|
|
return nil, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
|
|
}
|
|
|
|
f, l, fn := dbp.goSymTable.PCToLine(uint64(addr))
|
|
if fn == nil {
|
|
return nil, InvalidAddressError{address: addr}
|
|
}
|
|
|
|
newBreakpoint := &Breakpoint{
|
|
FunctionName: fn.Name,
|
|
File: f,
|
|
Line: l,
|
|
Addr: addr,
|
|
Kind: kind,
|
|
Cond: nil,
|
|
HitCount: map[int]uint64{},
|
|
}
|
|
|
|
if kind != UserBreakpoint {
|
|
dbp.tempBreakpointIDCounter++
|
|
newBreakpoint.ID = dbp.tempBreakpointIDCounter
|
|
} else {
|
|
dbp.breakpointIDCounter++
|
|
newBreakpoint.ID = dbp.breakpointIDCounter
|
|
}
|
|
|
|
thread := dbp.Threads[tid]
|
|
originalData, err := thread.readMemory(uintptr(addr), dbp.arch.BreakpointSize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dbp.writeSoftwareBreakpoint(thread, addr); err != nil {
|
|
return nil, err
|
|
}
|
|
newBreakpoint.OriginalData = originalData
|
|
dbp.Breakpoints[addr] = newBreakpoint
|
|
|
|
return newBreakpoint, nil
|
|
}
|
|
|
|
func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
|
|
_, err := thread.writeMemory(uintptr(addr), dbp.arch.BreakpointInstruction())
|
|
return err
|
|
}
|
|
|
|
func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) {
|
|
if bp.Cond == nil {
|
|
return true, nil
|
|
}
|
|
if bp.Kind == NextDeferBreakpoint {
|
|
frames, err := thread.Stacktrace(2)
|
|
if err == nil {
|
|
ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
|
|
isdeferreturn := false
|
|
if len(frames) >= 1 {
|
|
for _, pc := range bp.DeferReturns {
|
|
if frames[0].Ret == pc {
|
|
isdeferreturn = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !ispanic && !isdeferreturn {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
scope, err := thread.Scope()
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
v, err := scope.evalAST(bp.Cond)
|
|
if err != nil {
|
|
return true, fmt.Errorf("error evaluating expression: %v", err)
|
|
}
|
|
if v.Unreadable != nil {
|
|
return true, fmt.Errorf("condition expression unreadable: %v", v.Unreadable)
|
|
}
|
|
if v.Kind != reflect.Bool {
|
|
return true, errors.New("condition expression not boolean")
|
|
}
|
|
return constant.BoolVal(v.Value), nil
|
|
}
|
|
|
|
// Internal returns true for breakpoints not set directly by the user.
|
|
func (bp *Breakpoint) Internal() bool {
|
|
return bp.Kind != UserBreakpoint
|
|
}
|
|
|
|
// NoBreakpointError is returned when trying to
|
|
// clear a breakpoint that does not exist.
|
|
type NoBreakpointError struct {
|
|
addr uint64
|
|
}
|
|
|
|
func (nbp NoBreakpointError) Error() string {
|
|
return fmt.Sprintf("no breakpoint at %#v", nbp.addr)
|
|
}
|