delve/pkg/proc/breakpoints.go
Alessandro Arzilli db0bc26949
terminal,service: better printing of suspended breakpoints (#3415)
Show the location expression that will be used to set a suspended
breakpoint in the breakpoints list.

Also change 'target' called without arguments to print a better error
message and 'target follow-exec' without the last argument to print the
state of follow-exec.
2023-07-07 10:33:40 -07:00

1029 lines
29 KiB
Go

package proc
import (
"debug/dwarf"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/token"
"reflect"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/proc/internal/ebpf"
)
const (
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
UnrecoveredPanic = "unrecovered-panic"
// FatalThrow is the name given to the breakpoint triggered when the target
// process dies because of a fatal runtime error.
FatalThrow = "runtime-fatal-throw"
// HardcodedBreakpoint is the name given to hardcoded breakpoints (for
// example: calls to runtime.Breakpoint)
HardcodedBreakpoint = "hardcoded-breakpoint"
unrecoveredPanicID = -1
fatalThrowID = -2
hardcodedBreakpointID = -3
NoLogicalID = -1000 // Logical breakpoint ID for breakpoints internal breakpoints.
)
// Breakpoint represents a physical 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.
WatchExpr string
WatchType WatchType
HWBreakIndex uint8 // hardware breakpoint index
watchStackOff int64 // for watchpoints of stack variables, offset of the address from top of the stack
// Breaklets is the list of overlapping breakpoints on this physical breakpoint.
// There can be at most one UserBreakpoint in this list but multiple internal breakpoints are allowed.
Breaklets []*Breaklet
// Breakpoint information
Logical *LogicalBreakpoint
// ReturnInfo describes how to collect return variables when this
// breakpoint is hit as a return breakpoint.
returnInfo *returnBreakpointInfo
}
// Breaklet represents one of multiple breakpoints that can overlap on a
// single physical breakpoint.
type Breaklet struct {
// Kind describes whether this is a stepping breakpoint (for next'ing or
// stepping).
Kind BreakpointKind
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
// 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 ensures 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
// checkPanicCall checks that the breakpoint happened while the function was
// called by a panic. It is only checked for WatchOutOfScopeBreakpoint Kind.
checkPanicCall bool
// callback is called if every other condition for this breaklet is met,
// the return value will determine if the breaklet should be considered
// active.
// The callback can have side-effects.
callback func(th Thread, p *Target) (bool, error)
// For WatchOutOfScopeBreakpoints and StackResizeBreakpoints the watchpoint
// field contains the watchpoint related to this out of scope sentinel.
watchpoint *Breakpoint
}
// BreakpointKind determines the behavior of delve when the
// breakpoint is reached.
type BreakpointKind uint16
const (
// UserBreakpoint is a user set breakpoint
UserBreakpoint BreakpointKind = (1 << 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
// WatchOutOfScopeBreakpoint is a breakpoint used to detect when a watched
// stack variable goes out of scope.
WatchOutOfScopeBreakpoint
// StackResizeBreakpoint is a breakpoint used to detect stack resizes to
// adjust the watchpoint of stack variables.
StackResizeBreakpoint
// PluginOpenBreakpoint is a breakpoint used to detect that a plugin has
// been loaded and we should try to enable suspended breakpoints.
PluginOpenBreakpoint
steppingMask = NextBreakpoint | NextDeferBreakpoint | StepBreakpoint
)
// WatchType is the watchpoint type
type WatchType uint8
const (
WatchRead WatchType = 1 << iota
WatchWrite
)
// Read returns true if the hardware breakpoint should trigger on memory reads.
func (wtype WatchType) Read() bool {
return wtype&WatchRead != 0
}
// Write returns true if the hardware breakpoint should trigger on memory writes.
func (wtype WatchType) Write() bool {
return wtype&WatchWrite != 0
}
// Size returns the size in bytes of the hardware breakpoint.
func (wtype WatchType) Size() int {
return int(wtype >> 4)
}
// withSize returns a new HWBreakType with the size set to the specified value
func (wtype WatchType) withSize(sz uint8) WatchType {
return WatchType((sz << 4) | uint8(wtype&0xf))
}
var ErrHWBreakUnsupported = errors.New("hardware breakpoints not implemented")
func (bp *Breakpoint) String() string {
return fmt.Sprintf("Breakpoint %d at %#v %s:%d", bp.LogicalID(), bp.Addr, bp.File, bp.Line)
}
func (bp *Breakpoint) LogicalID() int {
for _, breaklet := range bp.Breaklets {
if breaklet.Kind == UserBreakpoint {
return breaklet.LogicalID
}
}
return NoLogicalID
}
// VerboseDescr returns a string describing parts of the breakpoint struct
// that aren't otherwise user visible, for debugging purposes.
func (bp *Breakpoint) VerboseDescr() []string {
r := []string{}
r = append(r, fmt.Sprintf("OriginalData=%#x", bp.OriginalData))
if bp.WatchType != 0 {
r = append(r, fmt.Sprintf("HWBreakIndex=%#x watchStackOff=%#x", bp.HWBreakIndex, bp.watchStackOff))
}
lbp := bp.Logical
for _, breaklet := range bp.Breaklets {
switch breaklet.Kind {
case UserBreakpoint:
r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", exprToString(breaklet.Cond), lbp.HitCond))
case NextBreakpoint:
r = append(r, fmt.Sprintf("Next Cond=%q", exprToString(breaklet.Cond)))
case NextDeferBreakpoint:
r = append(r, fmt.Sprintf("NextDefer Cond=%q DeferReturns=%#x", exprToString(breaklet.Cond), breaklet.DeferReturns))
case StepBreakpoint:
r = append(r, fmt.Sprintf("Step Cond=%q", exprToString(breaklet.Cond)))
case WatchOutOfScopeBreakpoint:
r = append(r, fmt.Sprintf("WatchOutOfScope Cond=%q checkPanicCall=%v", exprToString(breaklet.Cond), breaklet.checkPanicCall))
case StackResizeBreakpoint:
r = append(r, fmt.Sprintf("StackResizeBreakpoint Cond=%q", exprToString(breaklet.Cond)))
case PluginOpenBreakpoint:
r = append(r, "PluginOpenBreakpoint")
default:
r = append(r, fmt.Sprintf("Unknown %d", breaklet.Kind))
}
}
return r
}
// 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)
}
type returnBreakpointInfo struct {
retFrameCond ast.Expr
fn *Function
frameOffset int64
spOffset int64
}
// CheckCondition evaluates bp's condition on thread.
func (bp *Breakpoint) checkCondition(tgt *Target, thread Thread, bpstate *BreakpointState) {
*bpstate = BreakpointState{Breakpoint: bp, Active: false, Stepping: false, SteppingInto: false, CondError: nil}
for _, breaklet := range bp.Breaklets {
bpstate.checkCond(tgt, breaklet, thread)
}
}
func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, thread Thread) {
var condErr error
active := true
if breaklet.Cond != nil {
active, condErr = evalBreakpointCondition(tgt, thread, breaklet.Cond)
}
if condErr != nil && bpstate.CondError == nil {
bpstate.CondError = condErr
}
if !active {
return
}
switch breaklet.Kind {
case UserBreakpoint:
var goroutineID int64
lbp := bpstate.Breakpoint.Logical
if lbp != nil {
if g, err := GetG(thread); err == nil {
goroutineID = g.ID
lbp.HitCount[goroutineID]++
}
lbp.TotalHitCount++
}
active = checkHitCond(lbp, goroutineID)
case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint:
nextDeferOk := true
if breaklet.Kind&NextDeferBreakpoint != 0 {
var err error
frames, err := ThreadStacktrace(tgt, thread, 2)
if err == nil {
nextDeferOk, _ = isPanicCall(frames)
if !nextDeferOk {
nextDeferOk, _ = isDeferReturnCall(frames, breaklet.DeferReturns)
}
}
}
active = active && nextDeferOk
case WatchOutOfScopeBreakpoint:
if breaklet.checkPanicCall {
frames, err := ThreadStacktrace(tgt, thread, 2)
if err == nil {
ipc, _ := isPanicCall(frames)
active = active && ipc
}
}
case StackResizeBreakpoint, PluginOpenBreakpoint:
// no further checks
default:
bpstate.CondError = fmt.Errorf("internal error unknown breakpoint kind %v", breaklet.Kind)
}
if active {
if breaklet.callback != nil {
var err error
active, err = breaklet.callback(thread, tgt)
if err != nil && bpstate.CondError == nil {
bpstate.CondError = err
}
}
bpstate.Active = active
}
if bpstate.Active {
switch breaklet.Kind {
case NextBreakpoint, NextDeferBreakpoint:
bpstate.Stepping = true
case StepBreakpoint:
bpstate.Stepping = true
bpstate.SteppingInto = true
}
}
}
// checkHitCond evaluates bp's hit condition on thread.
func checkHitCond(lbp *LogicalBreakpoint, goroutineID int64) bool {
if lbp == nil || lbp.HitCond == nil {
return true
}
hitCount := int(lbp.TotalHitCount)
if lbp.HitCondPerG && goroutineID > 0 {
hitCount = int(lbp.HitCount[goroutineID])
}
// Evaluate the breakpoint condition.
switch lbp.HitCond.Op {
case token.EQL:
return hitCount == lbp.HitCond.Val
case token.NEQ:
return hitCount != lbp.HitCond.Val
case token.GTR:
return hitCount > lbp.HitCond.Val
case token.LSS:
return hitCount < lbp.HitCond.Val
case token.GEQ:
return hitCount >= lbp.HitCond.Val
case token.LEQ:
return hitCount <= lbp.HitCond.Val
case token.REM:
return hitCount%lbp.HitCond.Val == 0
}
return false
}
func isPanicCall(frames []Stackframe) (bool, int) {
// In Go prior to 1.17 the call stack for a panic is:
// 0. deferred function call
// 1. runtime.callN
// 2. runtime.gopanic
// in Go after 1.17 it is either:
// 0. deferred function call
// 1. deferred call wrapper
// 2. runtime.gopanic
// or:
// 0. deferred function call
// 1. runtime.gopanic
if len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic" {
return true, 2
}
if len(frames) >= 2 && frames[1].Current.Fn != nil && frames[1].Current.Fn.Name == "runtime.gopanic" {
return true, 1
}
return false, 0
}
func isDeferReturnCall(frames []Stackframe, deferReturns []uint64) (bool, uint64) {
if len(frames) >= 2 && (len(deferReturns) > 0) {
// On Go 1.18 and later runtime.deferreturn doesn't use jmpdefer anymore,
// it's a normal function making normal calls to deferred functions.
if frames[1].Current.Fn != nil && frames[1].Current.Fn.Name == "runtime.deferreturn" {
return true, 0
}
}
if len(frames) >= 1 {
for _, pc := range deferReturns {
if frames[0].Ret == pc {
return true, pc
}
}
}
return false, 0
}
// IsStepping returns true if bp is an stepping breakpoint.
// User-set breakpoints can overlap with stepping breakpoints, in that case
// both IsUser and IsStepping will be true.
func (bp *Breakpoint) IsStepping() bool {
for _, breaklet := range bp.Breaklets {
if breaklet.Kind&steppingMask != 0 {
return true
}
}
return false
}
// IsUser returns true if bp is a user-set breakpoint.
// User-set breakpoints can overlap with stepping breakpoints, in that case
// both IsUser and IsStepping will be true.
func (bp *Breakpoint) IsUser() bool {
for _, breaklet := range bp.Breaklets {
if breaklet.Kind == UserBreakpoint {
return true
}
}
return false
}
// UserBreaklet returns the user breaklet for this breakpoint, or nil if
// none exist.
func (bp *Breakpoint) UserBreaklet() *Breaklet {
for _, breaklet := range bp.Breaklets {
if breaklet.Kind == UserBreakpoint {
return breaklet
}
}
return nil
}
func evalBreakpointCondition(tgt *Target, thread Thread, cond ast.Expr) (bool, error) {
if cond == nil {
return true, nil
}
scope, err := GoroutineScope(tgt, thread)
if err != nil {
scope, err = ThreadScope(tgt, thread)
if err != nil {
return true, err
}
}
v, err := scope.evalAST(cond)
if err != nil {
return true, fmt.Errorf("error evaluating expression: %v", err)
}
if v.Kind != reflect.Bool {
return true, errors.New("condition expression not boolean")
}
v.loadValue(loadFullValue)
if v.Unreadable != nil {
return true, fmt.Errorf("condition expression unreadable: %v", v.Unreadable)
}
return constant.BoolVal(v.Value), nil
}
// 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)
}
// BreakpointMap represents an (address, breakpoint) map.
type BreakpointMap struct {
M map[uint64]*Breakpoint
// Logical is a map of logical breakpoints.
Logical map[int]*LogicalBreakpoint
// WatchOutOfScope is the list of watchpoints that went out of scope during
// the last resume operation
WatchOutOfScope []*Breakpoint
}
// NewBreakpointMap creates a new BreakpointMap.
func NewBreakpointMap() BreakpointMap {
return BreakpointMap{
M: make(map[uint64]*Breakpoint),
}
}
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
// break point table.
func (t *Target) SetBreakpoint(logicalID int, addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) {
return t.setBreakpointInternal(logicalID, addr, kind, 0, cond)
}
// SetEBPFTracepoint will attach a uprobe to the function
// specified by 'fnName'.
func (t *Target) SetEBPFTracepoint(fnName string) error {
// Not every OS/arch that we support has support for eBPF,
// so check early and return an error if this is called on an
// unsupported system.
if !t.proc.SupportsBPF() {
return errors.New("eBPF is not supported")
}
fns, err := t.BinInfo().FindFunction(fnName)
if err != nil {
return err
}
// Get information on the Goroutine so we can tell the
// eBPF program where to find it in order to get the
// goroutine ID.
rdr := t.BinInfo().Images[0].DwarfReader()
rdr.SeekToTypeNamed("runtime.g")
typ, err := t.BinInfo().findType("runtime.g")
if err != nil {
return errors.New("could not find type for runtime.g")
}
var goidOffset int64
switch t := typ.(type) {
case *godwarf.StructType:
for _, field := range t.Field {
if field.Name == "goid" {
goidOffset = field.ByteOffset
break
}
}
}
for _, fn := range fns {
err := t.setEBPFTracepointOnFunc(fn, goidOffset)
if err != nil {
return err
}
}
return nil
}
func (t *Target) setEBPFTracepointOnFunc(fn *Function, goidOffset int64) error {
// Start putting together the argument map. This will tell the eBPF program
// all of the arguments we want to trace and how to find them.
// Start looping through each argument / return parameter for the function we
// are setting the uprobe on. Parse location information so that we can pass it
// along to the eBPF program.
dwarfTree, err := fn.cu.image.getDwarfTree(fn.offset)
if err != nil {
return err
}
variablesFlags := reader.VariablesOnlyVisible
if t.BinInfo().Producer() != "" && goversion.ProducerAfterOrEqual(t.BinInfo().Producer(), 1, 15) {
variablesFlags |= reader.VariablesTrustDeclLine
}
_, l := t.BinInfo().EntryLineForFunc(fn)
var args []ebpf.UProbeArgMap
varEntries := reader.Variables(dwarfTree, fn.Entry, l, variablesFlags)
for _, entry := range varEntries {
_, dt, err := readVarEntry(entry.Tree, fn.cu.image)
if err != nil {
return err
}
offset, pieces, _, err := t.BinInfo().Location(entry, dwarf.AttrLocation, fn.Entry, op.DwarfRegisters{}, nil)
if err != nil {
return err
}
paramPieces := make([]int, 0, len(pieces))
for _, piece := range pieces {
if piece.Kind == op.RegPiece {
paramPieces = append(paramPieces, int(piece.Val))
}
}
isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
offset += int64(t.BinInfo().Arch.PtrSize())
args = append(args, ebpf.UProbeArgMap{
Offset: offset,
Size: dt.Size(),
Kind: dt.Common().ReflectKind,
Pieces: paramPieces,
InReg: len(pieces) > 0,
Ret: isret,
})
}
//TODO(aarzilli): inlined calls?
// Finally, set the uprobe on the function.
return t.proc.SetUProbe(fn.Name, goidOffset, args)
}
// SetWatchpoint sets a data breakpoint at addr and stores it in the
// process wide break point table.
func (t *Target) SetWatchpoint(logicalID int, scope *EvalScope, expr string, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
if (wtype&WatchWrite == 0) && (wtype&WatchRead == 0) {
return nil, errors.New("at least one of read and write must be set for watchpoint")
}
n, err := parser.ParseExpr(expr)
if err != nil {
return nil, err
}
xv, err := scope.evalAST(n)
if err != nil {
return nil, err
}
if xv.Addr == 0 || xv.Flags&VariableFakeAddress != 0 || xv.DwarfType == nil {
return nil, fmt.Errorf("can not watch %q", expr)
}
if xv.Unreadable != nil {
return nil, fmt.Errorf("expression %q is unreadable: %v", expr, xv.Unreadable)
}
if xv.Kind == reflect.UnsafePointer || xv.Kind == reflect.Invalid {
return nil, fmt.Errorf("can not watch variable of type %s", xv.Kind.String())
}
sz := xv.DwarfType.Size()
if sz <= 0 || sz > int64(t.BinInfo().Arch.PtrSize()) {
//TODO(aarzilli): it is reasonable to expect to be able to watch string
//and interface variables and we could support it by watching certain
//member fields here.
return nil, fmt.Errorf("can not watch variable of type %s", xv.DwarfType.String())
}
stackWatch := scope.g != nil && !scope.g.SystemStack && xv.Addr >= scope.g.stack.lo && xv.Addr < scope.g.stack.hi
if stackWatch && wtype&WatchRead != 0 {
// In theory this would work except for the fact that the runtime will
// read them randomly to resize stacks so it doesn't make sense to do
// this.
return nil, errors.New("can not watch stack allocated variable for reads")
}
bp, err := t.setBreakpointInternal(logicalID, xv.Addr, UserBreakpoint, wtype.withSize(uint8(sz)), cond)
if err != nil {
return bp, err
}
bp.WatchExpr = expr
if stackWatch {
bp.watchStackOff = int64(bp.Addr) - int64(scope.g.stack.hi)
err := t.setStackWatchBreakpoints(scope, bp)
if err != nil {
return bp, err
}
}
return bp, nil
}
func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind BreakpointKind, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
if valid, err := t.Valid(); !valid {
recorded, _ := t.recman.Recorded()
if !recorded {
return nil, err
}
}
bpmap := t.Breakpoints()
newBreaklet := &Breaklet{Kind: kind, Cond: cond}
if kind == UserBreakpoint {
newBreaklet.LogicalID = logicalID
}
setLogicalBreakpoint := func(bp *Breakpoint) {
if kind != UserBreakpoint || bp.Logical != nil {
return
}
if bpmap.Logical == nil {
bpmap.Logical = make(map[int]*LogicalBreakpoint)
}
lbp := bpmap.Logical[logicalID]
if lbp == nil {
lbp = &LogicalBreakpoint{LogicalID: logicalID}
lbp.HitCount = make(map[int64]uint64)
lbp.Enabled = true
bpmap.Logical[logicalID] = lbp
}
bp.Logical = lbp
breaklet := bp.UserBreaklet()
if breaklet != nil && breaklet.Cond == nil {
breaklet.Cond = lbp.Cond
}
if lbp.File == "" && lbp.Line == 0 {
lbp.File = bp.File
lbp.Line = bp.Line
} else if bp.File != lbp.File || bp.Line != lbp.Line {
lbp.File = "<multiple locations>"
lbp.Line = 0
}
fn := t.BinInfo().PCToFunc(bp.Addr)
if fn != nil {
lbp.FunctionName = fn.NameWithoutTypeParams()
}
}
if bp, ok := bpmap.M[addr]; ok {
if !bp.canOverlap(kind) {
return bp, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
}
bp.Breaklets = append(bp.Breaklets, newBreaklet)
setLogicalBreakpoint(bp)
return bp, nil
}
f, l, fn := t.BinInfo().PCToLine(uint64(addr))
fnName := ""
if fn != nil {
fnName = fn.Name
}
hwidx := uint8(0)
if wtype != 0 {
m := make(map[uint8]bool)
for _, bp := range bpmap.M {
if bp.WatchType != 0 {
m[bp.HWBreakIndex] = true
}
}
for hwidx = 0; true; hwidx++ {
if !m[hwidx] {
break
}
}
}
newBreakpoint := &Breakpoint{
FunctionName: fnName,
WatchType: wtype,
HWBreakIndex: hwidx,
File: f,
Line: l,
Addr: addr,
}
err := t.proc.WriteBreakpoint(newBreakpoint)
if err != nil {
return nil, err
}
newBreakpoint.Breaklets = append(newBreakpoint.Breaklets, newBreaklet)
setLogicalBreakpoint(newBreakpoint)
bpmap.M[addr] = newBreakpoint
return newBreakpoint, nil
}
// canOverlap returns true if a breakpoint of kind can be overlapped to the
// already existing breaklets in bp.
// At most one user breakpoint can be set but multiple internal breakpoints are allowed.
// All other internal breakpoints are allowed to overlap freely.
func (bp *Breakpoint) canOverlap(kind BreakpointKind) bool {
if kind == UserBreakpoint {
return !bp.IsUser()
}
return true
}
// ClearBreakpoint clears the breakpoint at addr.
func (t *Target) ClearBreakpoint(addr uint64) error {
if valid, err := t.Valid(); !valid {
recorded, _ := t.recman.Recorded()
if !recorded {
return err
}
}
bp, ok := t.Breakpoints().M[addr]
if !ok {
return NoBreakpointError{Addr: addr}
}
for i := range bp.Breaklets {
if bp.Breaklets[i].Kind == UserBreakpoint {
bp.Breaklets[i] = nil
if bp.WatchExpr == "" {
bp.Logical = nil
}
}
}
_, err := t.finishClearBreakpoint(bp)
if err != nil {
return err
}
if bp.WatchExpr != "" && bp.watchStackOff != 0 {
// stack watchpoint, must remove all its WatchOutOfScopeBreakpoints/StackResizeBreakpoints
err := t.clearStackWatchBreakpoints(bp)
if err != nil {
return err
}
}
return nil
}
// ClearSteppingBreakpoints removes all stepping breakpoints from the map,
// calling clearBreakpoint on each one.
func (t *Target) ClearSteppingBreakpoints() error {
bpmap := t.Breakpoints()
threads := t.ThreadList()
for _, bp := range bpmap.M {
for i := range bp.Breaklets {
if bp.Breaklets[i].Kind&steppingMask != 0 {
bp.Breaklets[i] = nil
}
}
cleared, err := t.finishClearBreakpoint(bp)
if err != nil {
return err
}
if cleared {
for _, thread := range threads {
if thread.Breakpoint().Breakpoint == bp {
thread.Breakpoint().Clear()
}
}
}
}
return nil
}
// finishClearBreakpoint clears nil breaklets from the breaklet list of bp
// and if it is empty erases the breakpoint.
// Returns true if the breakpoint was deleted
func (t *Target) finishClearBreakpoint(bp *Breakpoint) (bool, error) {
oldBreaklets := bp.Breaklets
bp.Breaklets = bp.Breaklets[:0]
for _, breaklet := range oldBreaklets {
if breaklet != nil {
bp.Breaklets = append(bp.Breaklets, breaklet)
}
}
if len(bp.Breaklets) > 0 {
return false, nil
}
if err := t.proc.EraseBreakpoint(bp); err != nil {
return false, err
}
delete(t.Breakpoints().M, bp.Addr)
if bp.WatchExpr != "" && bp.Logical != nil {
delete(t.Breakpoints().Logical, bp.Logical.LogicalID)
}
return true, nil
}
// HasSteppingBreakpoints returns true if bpmap has at least one stepping
// breakpoint set.
func (bpmap *BreakpointMap) HasSteppingBreakpoints() bool {
for _, bp := range bpmap.M {
if bp.IsStepping() {
return true
}
}
return false
}
// HasHWBreakpoints returns true if there are hardware breakpoints.
func (bpmap *BreakpointMap) HasHWBreakpoints() bool {
for _, bp := range bpmap.M {
if bp.WatchType != 0 {
return true
}
}
return false
}
// BreakpointState describes the state of a breakpoint in a thread.
type BreakpointState struct {
*Breakpoint
// Active is true if the condition of any breaklet is met.
Active bool
// Stepping is true if one of the active breaklets is a stepping
// breakpoint.
Stepping bool
// SteppingInto is true if one of the active stepping breaklets has Kind ==
// StepBreakpoint.
SteppingInto bool
// CondError contains any error encountered while evaluating the
// breakpoint's condition.
CondError error
}
// Clear zeros the struct.
func (bpstate *BreakpointState) Clear() {
bpstate.Breakpoint = nil
bpstate.Active = false
bpstate.Stepping = false
bpstate.SteppingInto = false
bpstate.CondError = nil
}
func (bpstate *BreakpointState) String() string {
s := bpstate.Breakpoint.String()
if bpstate.Active {
s += " active"
}
if bpstate.Stepping {
s += " stepping"
}
return s
}
func configureReturnBreakpoint(bi *BinaryInfo, bp *Breakpoint, topframe *Stackframe, retFrameCond ast.Expr) {
if topframe.Current.Fn == nil {
return
}
bp.returnInfo = &returnBreakpointInfo{
retFrameCond: retFrameCond,
fn: topframe.Current.Fn,
frameOffset: topframe.FrameOffset(),
spOffset: topframe.FrameOffset() - int64(bi.Arch.PtrSize()), // must be the value that SP had at the entry point of the function
}
}
func (rbpi *returnBreakpointInfo) Collect(t *Target, thread Thread) []*Variable {
if rbpi == nil {
return nil
}
g, err := GetG(thread)
if err != nil {
return returnInfoError("could not get g", err, thread.ProcessMemory())
}
scope, err := GoroutineScope(t, thread)
if err != nil {
return returnInfoError("could not get scope", err, thread.ProcessMemory())
}
v, err := scope.evalAST(rbpi.retFrameCond)
if err != nil || v.Unreadable != nil || v.Kind != reflect.Bool {
// This condition was evaluated as part of the breakpoint condition
// evaluation, if the errors happen they will be reported as part of the
// condition errors.
return nil
}
if !constant.BoolVal(v.Value) {
// Breakpoint not hit as a return breakpoint.
return nil
}
oldFrameOffset := rbpi.frameOffset + int64(g.stack.hi)
oldSP := uint64(rbpi.spOffset + int64(g.stack.hi))
err = fakeFunctionEntryScope(scope, rbpi.fn, oldFrameOffset, oldSP)
if err != nil {
return returnInfoError("could not read function entry", err, thread.ProcessMemory())
}
vars, err := scope.Locals(0)
if err != nil {
return returnInfoError("could not evaluate return variables", err, thread.ProcessMemory())
}
vars = filterVariables(vars, func(v *Variable) bool {
return (v.Flags & VariableReturnArgument) != 0
})
return vars
}
func returnInfoError(descr string, err error, mem MemoryReadWriter) []*Variable {
v := newConstant(constant.MakeString(fmt.Sprintf("%s: %v", descr, err.Error())), mem)
v.Name = "return value read error"
return []*Variable{v}
}
// LogicalBreakpoint represents a breakpoint set by a user.
// A logical breakpoint can be associated with zero or many physical
// breakpoints.
// Where a physical breakpoint is associated with a specific instruction
// address a logical breakpoint is associated with a source code location.
// Therefore a logical breakpoint can be associated with zero or many
// physical breakpoints.
// It will have one or more physical breakpoints when source code has been
// inlined (or in the case of type parametric code).
// It will have zero physical breakpoints when it represents a deferred
// breakpoint for code that will be loaded in the future.
type LogicalBreakpoint struct {
LogicalID int
Name string
FunctionName string
File string
Line int
Enabled bool
Set SetBreakpoint
Tracepoint bool // Tracepoint flag
TraceReturn bool
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
HitCount map[int64]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached
HitCondPerG bool // Use per goroutine hitcount as HitCond operand, instead of total hitcount
// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
// true with the TotalHitCount.
HitCond *struct {
Op token.Token
Val int
}
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
UserData interface{} // Any additional information about the breakpoint
}
// SetBreakpoint describes how a breakpoint should be set.
type SetBreakpoint struct {
FunctionName string
File string
Line int
Expr func(*Target) []uint64
ExprString string
PidAddrs []PidAddr
}
type PidAddr struct {
Pid int
Addr uint64
}