mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 02:36:18 +08:00 
			
		
		
		
	 6e7e1d8830
			
		
	
	6e7e1d8830
	
	
	
		
			
			Allows setting suspended breakpoints and try to enable them automatically after every time a plugin is loaded. Fixes #1653 Updates #2551
		
			
				
	
	
		
			1015 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1015 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) bool
 | |
| 
 | |
| 	// 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(thread, 2)
 | |
| 			if err == nil {
 | |
| 				nextDeferOk, _ = isPanicCall(frames)
 | |
| 				if !nextDeferOk {
 | |
| 					nextDeferOk, _ = isDeferReturnCall(frames, breaklet.DeferReturns)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		active = active && nextDeferOk
 | |
| 		if active {
 | |
| 			bpstate.Stepping = true
 | |
| 			if breaklet.Kind == StepBreakpoint {
 | |
| 				bpstate.SteppingInto = true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	case WatchOutOfScopeBreakpoint:
 | |
| 		if breaklet.checkPanicCall {
 | |
| 			frames, err := ThreadStacktrace(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 {
 | |
| 			active = breaklet.callback(thread)
 | |
| 		}
 | |
| 		bpstate.Active = active
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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().PCToLine(fn.Entry)
 | |
| 
 | |
| 	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
 | |
| 		}
 | |
| 		lbp.File = bp.File
 | |
| 		lbp.Line = bp.Line
 | |
| 		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
 | |
| 	PidAddrs     []PidAddr
 | |
| }
 | |
| 
 | |
| type PidAddr struct {
 | |
| 	Pid  int
 | |
| 	Addr uint64
 | |
| }
 |