proc/native: low level support for watchpoints in linux/amd64 (#2301)
Adds the low-level support for watchpoints (aka data breakpoints) to the native linux/amd64 backend. Does not add user interface or functioning support for watchpoints on stack variables. Updates #279
This commit is contained in:
parent
1962c3a627
commit
58762685e3
@ -1,23 +1,30 @@
|
|||||||
Tests skipped by each supported backend:
|
Tests skipped by each supported backend:
|
||||||
|
|
||||||
* 386 skipped = 2.7% (4/147)
|
* 386 skipped = 4% (6/149)
|
||||||
* 1 broken
|
* 1 broken
|
||||||
* 3 broken - cgo stacktraces
|
* 3 broken - cgo stacktraces
|
||||||
* arm64 skipped = 2% (3/147)
|
* 2 not implemented
|
||||||
|
* arm64 skipped = 3.4% (5/149)
|
||||||
* 1 broken
|
* 1 broken
|
||||||
* 1 broken - cgo stacktraces
|
* 1 broken - cgo stacktraces
|
||||||
* 1 broken - global variable symbolication
|
* 1 broken - global variable symbolication
|
||||||
* darwin/arm64 skipped = 0.68% (1/147)
|
* 2 not implemented
|
||||||
|
* darwin skipped = 1.3% (2/149)
|
||||||
|
* 2 not implemented
|
||||||
|
* darwin/arm64 skipped = 0.67% (1/149)
|
||||||
* 1 broken - cgo stacktraces
|
* 1 broken - cgo stacktraces
|
||||||
* darwin/lldb skipped = 0.68% (1/147)
|
* darwin/lldb skipped = 0.67% (1/149)
|
||||||
* 1 upstream issue
|
* 1 upstream issue
|
||||||
* freebsd skipped = 8.2% (12/147)
|
* freebsd skipped = 9.4% (14/149)
|
||||||
* 11 broken
|
* 11 broken
|
||||||
* 1 not implemented
|
* 3 not implemented
|
||||||
* linux/386/pie skipped = 0.68% (1/147)
|
* linux/386/pie skipped = 0.67% (1/149)
|
||||||
* 1 broken
|
* 1 broken
|
||||||
* pie skipped = 0.68% (1/147)
|
* pie skipped = 0.67% (1/149)
|
||||||
* 1 upstream issue - https://github.com/golang/go/issues/29322
|
* 1 upstream issue - https://github.com/golang/go/issues/29322
|
||||||
* windows skipped = 1.4% (2/147)
|
* rr skipped = 1.3% (2/149)
|
||||||
|
* 2 not implemented
|
||||||
|
* windows skipped = 2.7% (4/149)
|
||||||
* 1 broken
|
* 1 broken
|
||||||
|
* 2 not implemented
|
||||||
* 1 upstream issue
|
* 1 upstream issue
|
||||||
|
|||||||
31
_fixtures/databpcountstest.go
Normal file
31
_fixtures/databpcountstest.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalvar1 int
|
||||||
|
|
||||||
|
func demo(id int, wait *sync.WaitGroup) {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
sleep := rand.Intn(10) + 1
|
||||||
|
fmt.Printf("id: %d step: %d sleeping %d\n", id, i, sleep)
|
||||||
|
globalvar1 = globalvar1 + 1
|
||||||
|
time.Sleep(time.Duration(sleep) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
wait.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
wait := new(sync.WaitGroup)
|
||||||
|
wait.Add(1)
|
||||||
|
wait.Add(1)
|
||||||
|
go demo(1, wait)
|
||||||
|
go demo(2, wait)
|
||||||
|
|
||||||
|
wait.Wait()
|
||||||
|
}
|
||||||
34
_fixtures/databpeasy.go
Normal file
34
_fixtures/databpeasy.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalvar1 = 0
|
||||||
|
var globalvar2 = 0
|
||||||
|
|
||||||
|
func main() { // Position 0
|
||||||
|
runtime.LockOSThread()
|
||||||
|
globalvar2 = 1
|
||||||
|
fmt.Printf("%d %d\n", globalvar1, globalvar2)
|
||||||
|
globalvar2 = globalvar1 + 1
|
||||||
|
globalvar1 = globalvar2 + 1
|
||||||
|
fmt.Printf("%d %d\n", globalvar1, globalvar2) // Position 1
|
||||||
|
runtime.Breakpoint()
|
||||||
|
globalvar2 = globalvar2 + 1 // Position 2
|
||||||
|
globalvar2 = globalvar1 + globalvar2 // Position 3
|
||||||
|
fmt.Printf("%d %d\n", globalvar1, globalvar2)
|
||||||
|
globalvar1 = globalvar2 + 1
|
||||||
|
fmt.Printf("%d %d\n", globalvar1, globalvar2)
|
||||||
|
runtime.Breakpoint()
|
||||||
|
done := make(chan struct{}) // Position 4
|
||||||
|
go f(done)
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func f(done chan struct{}) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
globalvar1 = globalvar2 + 1
|
||||||
|
close(done) // Position 5
|
||||||
|
}
|
||||||
130
pkg/proc/amd64util/debugregs.go
Normal file
130
pkg/proc/amd64util/debugregs.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package amd64util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugRegisters represents x86 debug registers described in the Intel 64
|
||||||
|
// and IA-32 Architectures Software Developer's Manual, Vol. 3B, section
|
||||||
|
// 17.2
|
||||||
|
type DebugRegisters struct {
|
||||||
|
pAddrs [4]*uint64
|
||||||
|
pDR6, pDR7 *uint64
|
||||||
|
Dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDebugRegisters(pDR0, pDR1, pDR2, pDR3, pDR6, pDR7 *uint64) *DebugRegisters {
|
||||||
|
return &DebugRegisters{
|
||||||
|
pAddrs: [4]*uint64{pDR0, pDR1, pDR2, pDR3},
|
||||||
|
pDR6: pDR6,
|
||||||
|
pDR7: pDR7,
|
||||||
|
Dirty: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lenrwBitsOffset(idx uint8) uint8 {
|
||||||
|
return 16 + idx*4
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableBitOffset(idx uint8) uint8 {
|
||||||
|
return idx * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (drs *DebugRegisters) breakpoint(idx uint8) (addr uint64, read, write bool, sz int) {
|
||||||
|
enable := *(drs.pDR7) & (1 << enableBitOffset(idx))
|
||||||
|
if enable == 0 {
|
||||||
|
return 0, false, false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = *(drs.pAddrs[idx])
|
||||||
|
lenrw := (*(drs.pDR7) >> lenrwBitsOffset(idx)) & 0xf
|
||||||
|
write = (lenrw & 0x1) != 0
|
||||||
|
read = (lenrw & 0x2) != 0
|
||||||
|
switch lenrw >> 2 {
|
||||||
|
case 0x0:
|
||||||
|
sz = 1
|
||||||
|
case 0x1:
|
||||||
|
sz = 2
|
||||||
|
case 0x2:
|
||||||
|
sz = 8 // sic
|
||||||
|
case 0x3:
|
||||||
|
sz = 4
|
||||||
|
}
|
||||||
|
return addr, read, write, sz
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBreakpoint sets hardware breakpoint at index 'idx' to the specified
|
||||||
|
// address, read/write flags and size.
|
||||||
|
// If the breakpoint is already in use but the parameters match it does
|
||||||
|
// nothing.
|
||||||
|
func (drs *DebugRegisters) SetBreakpoint(idx uint8, addr uint64, read, write bool, sz int) error {
|
||||||
|
if int(idx) >= len(drs.pAddrs) {
|
||||||
|
return fmt.Errorf("hardware breakpoints exhausted")
|
||||||
|
}
|
||||||
|
curaddr, curread, curwrite, cursz := drs.breakpoint(idx)
|
||||||
|
if curaddr != 0 {
|
||||||
|
if (curaddr != addr) || (curread != read) || (curwrite != write) || (cursz != sz) {
|
||||||
|
return fmt.Errorf("hardware breakpoint %d already in use (address %#x)", idx, curaddr)
|
||||||
|
}
|
||||||
|
// hardware breakpoint already set
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if read && !write {
|
||||||
|
return errors.New("break on read only not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
*(drs.pAddrs[idx]) = addr
|
||||||
|
var lenrw uint64
|
||||||
|
if write {
|
||||||
|
lenrw |= 0x1
|
||||||
|
}
|
||||||
|
if read {
|
||||||
|
lenrw |= 0x2
|
||||||
|
}
|
||||||
|
switch sz {
|
||||||
|
case 1:
|
||||||
|
// already ok
|
||||||
|
case 2:
|
||||||
|
lenrw |= 0x1 << 2
|
||||||
|
case 4:
|
||||||
|
lenrw |= 0x3 << 2
|
||||||
|
case 8:
|
||||||
|
lenrw |= 0x2 << 2
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("data breakpoint of size %d not supported", sz)
|
||||||
|
}
|
||||||
|
*(drs.pDR7) &^= (0xf << lenrwBitsOffset(idx)) // clear old settings
|
||||||
|
*(drs.pDR7) |= lenrw << lenrwBitsOffset(idx)
|
||||||
|
*(drs.pDR7) |= 1 << enableBitOffset(idx) // enable
|
||||||
|
drs.Dirty = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBreakpoint disables the hardware breakpoint at index 'idx'. If the
|
||||||
|
// breakpoint was already disabled it does nothing.
|
||||||
|
func (drs *DebugRegisters) ClearBreakpoint(idx uint8) {
|
||||||
|
if *(drs.pDR7)&(1<<enableBitOffset(idx)) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*(drs.pDR7) &^= (1 << enableBitOffset(idx))
|
||||||
|
drs.Dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveBreakpoint returns the active hardware breakpoint and resets the
|
||||||
|
// condition flags.
|
||||||
|
func (drs *DebugRegisters) GetActiveBreakpoint() (ok bool, idx uint8) {
|
||||||
|
for idx := uint8(0); idx < 3; idx++ {
|
||||||
|
enable := *(drs.pDR7) & (1 << enableBitOffset(idx))
|
||||||
|
if enable == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *(drs.pDR6)&(1<<idx) != 0 {
|
||||||
|
*drs.pDR6 &^= 0xf // it is our responsibility to clear the condition bits
|
||||||
|
drs.Dirty = true
|
||||||
|
return true, idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
|
"go/parser"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ type Breakpoint struct {
|
|||||||
Name string // User defined name of the breakpoint
|
Name string // User defined name of the breakpoint
|
||||||
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
|
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
|
||||||
|
|
||||||
|
WatchType WatchType
|
||||||
|
HWBreakIndex uint8 // hardware breakpoint index
|
||||||
|
|
||||||
// Kind describes whether this is an internal breakpoint (for next'ing or
|
// Kind describes whether this is an internal breakpoint (for next'ing or
|
||||||
// stepping).
|
// stepping).
|
||||||
// A single breakpoint can be both a UserBreakpoint and some kind of
|
// A single breakpoint can be both a UserBreakpoint and some kind of
|
||||||
@ -93,6 +97,36 @@ const (
|
|||||||
StepBreakpoint
|
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 {
|
func (bp *Breakpoint) String() string {
|
||||||
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.LogicalID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
|
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.LogicalID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
|
||||||
}
|
}
|
||||||
@ -243,6 +277,49 @@ func NewBreakpointMap() BreakpointMap {
|
|||||||
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
|
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
|
||||||
// break point table.
|
// break point table.
|
||||||
func (t *Target) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) {
|
func (t *Target) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) {
|
||||||
|
return t.setBreakpointInternal(addr, kind, 0, cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWatchpoint sets a data breakpoint at addr and stores it in the
|
||||||
|
// process wide break point table.
|
||||||
|
func (t *Target) SetWatchpoint(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())
|
||||||
|
}
|
||||||
|
if xv.Addr >= scope.g.stack.lo && xv.Addr < scope.g.stack.hi {
|
||||||
|
//TODO(aarzilli): support watching stack variables
|
||||||
|
return nil, errors.New("can not watch stack allocated variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.setBreakpointInternal(xv.Addr, UserBreakpoint, wtype.withSize(uint8(sz)), cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Target) setBreakpointInternal(addr uint64, kind BreakpointKind, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
|
||||||
if valid, err := t.Valid(); !valid {
|
if valid, err := t.Valid(); !valid {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -270,8 +347,25 @@ func (t *Target) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr)
|
|||||||
fnName = fn.Name
|
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{
|
newBreakpoint := &Breakpoint{
|
||||||
FunctionName: fnName,
|
FunctionName: fnName,
|
||||||
|
WatchType: wtype,
|
||||||
|
HWBreakIndex: hwidx,
|
||||||
File: f,
|
File: f,
|
||||||
Line: l,
|
Line: l,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
@ -372,6 +466,16 @@ func (bpmap *BreakpointMap) HasInternalBreakpoints() bool {
|
|||||||
return false
|
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.
|
// BreakpointState describes the state of a breakpoint in a thread.
|
||||||
type BreakpointState struct {
|
type BreakpointState struct {
|
||||||
*Breakpoint
|
*Breakpoint
|
||||||
|
|||||||
@ -1209,6 +1209,9 @@ func (p *gdbProcess) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *gdbProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
|
func (p *gdbProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
|
||||||
|
if bp.WatchType != 0 {
|
||||||
|
return errors.New("hardware breakpoints not supported")
|
||||||
|
}
|
||||||
return p.conn.setBreakpoint(bp.Addr, p.breakpointKind)
|
return p.conn.setBreakpoint(bp.Addr, p.breakpointKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -114,6 +114,18 @@ func (t *nativeThread) restoreRegisters(sr proc.Registers) error {
|
|||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
panic(ErrNativeBackendDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
panic(ErrNativeBackendDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
panic(ErrNativeBackendDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
// Stopped returns whether the thread is stopped at
|
// Stopped returns whether the thread is stopped at
|
||||||
// the operating system level.
|
// the operating system level.
|
||||||
func (t *nativeThread) Stopped() bool {
|
func (t *nativeThread) Stopped() bool {
|
||||||
|
|||||||
@ -206,6 +206,16 @@ func (dbp *nativeProcess) CheckAndClearManualStopRequest() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
|
func (dbp *nativeProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
|
||||||
|
if bp.WatchType != 0 {
|
||||||
|
for _, thread := range dbp.threads {
|
||||||
|
err := thread.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
bp.OriginalData = make([]byte, dbp.bi.Arch.BreakpointSize())
|
bp.OriginalData = make([]byte, dbp.bi.Arch.BreakpointSize())
|
||||||
_, err := dbp.memthread.ReadMemory(bp.OriginalData, bp.Addr)
|
_, err := dbp.memthread.ReadMemory(bp.OriginalData, bp.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -215,7 +225,17 @@ func (dbp *nativeProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
|
func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
|
||||||
return dbp.memthread.ClearBreakpoint(bp)
|
if bp.WatchType != 0 {
|
||||||
|
for _, thread := range dbp.threads {
|
||||||
|
err := thread.clearHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbp.memthread.clearSoftwareBreakpoint(bp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContinueOnce will continue the target until it stops.
|
// ContinueOnce will continue the target until it stops.
|
||||||
|
|||||||
@ -268,6 +268,14 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error)
|
|||||||
if dbp.memthread == nil {
|
if dbp.memthread == nil {
|
||||||
dbp.memthread = dbp.threads[tid]
|
dbp.memthread = dbp.threads[tid]
|
||||||
}
|
}
|
||||||
|
for _, bp := range dbp.Breakpoints().M {
|
||||||
|
if bp.WatchType != 0 {
|
||||||
|
err := dbp.threads[tid].writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return dbp.threads[tid], nil
|
return dbp.threads[tid], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,17 @@ func (t *nativeThread) StepInstruction() (err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
t.singleStepping = false
|
t.singleStepping = false
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if bp := t.CurrentBreakpoint.Breakpoint; bp != nil && bp.WatchType != 0 && t.dbp.Breakpoints().M[bp.Addr] == bp {
|
||||||
|
err = t.clearHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = t.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
pc, err := t.PC()
|
pc, err := t.PC()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -61,7 +72,7 @@ func (t *nativeThread) StepInstruction() (err error) {
|
|||||||
bp, ok := t.dbp.FindBreakpoint(pc, false)
|
bp, ok := t.dbp.FindBreakpoint(pc, false)
|
||||||
if ok {
|
if ok {
|
||||||
// Clear the breakpoint so that we can continue execution.
|
// Clear the breakpoint so that we can continue execution.
|
||||||
err = t.ClearBreakpoint(bp)
|
err = t.clearSoftwareBreakpoint(bp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -109,23 +120,40 @@ func (t *nativeThread) Common() *proc.CommonThread {
|
|||||||
// thread is stopped at as CurrentBreakpoint on the thread struct.
|
// thread is stopped at as CurrentBreakpoint on the thread struct.
|
||||||
func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error {
|
func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error {
|
||||||
t.CurrentBreakpoint.Clear()
|
t.CurrentBreakpoint.Clear()
|
||||||
pc, err := t.PC()
|
|
||||||
if err != nil {
|
var bp *proc.Breakpoint
|
||||||
return err
|
|
||||||
|
if t.dbp.Breakpoints().HasHWBreakpoints() {
|
||||||
|
var err error
|
||||||
|
bp, err = t.findHardwareBreakpoint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if bp == nil {
|
||||||
|
pc, err := t.PC()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// If the breakpoint instruction does not change the value
|
// If the breakpoint instruction does not change the value
|
||||||
// of PC after being executed we should look for breakpoints
|
// of PC after being executed we should look for breakpoints
|
||||||
// with bp.Addr == PC and there is no need to call SetPC
|
// with bp.Addr == PC and there is no need to call SetPC
|
||||||
// after finding one.
|
// after finding one.
|
||||||
adjustPC = adjustPC && t.BinInfo().Arch.BreakInstrMovesPC()
|
adjustPC = adjustPC && t.BinInfo().Arch.BreakInstrMovesPC()
|
||||||
|
|
||||||
if bp, ok := t.dbp.FindBreakpoint(pc, adjustPC); ok {
|
var ok bool
|
||||||
if adjustPC {
|
bp, ok = t.dbp.FindBreakpoint(pc, adjustPC)
|
||||||
if err = t.setPC(bp.Addr); err != nil {
|
if ok {
|
||||||
return err
|
if adjustPC {
|
||||||
|
if err = t.setPC(bp.Addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bp != nil {
|
||||||
t.CurrentBreakpoint = bp.CheckCondition(t)
|
t.CurrentBreakpoint = bp.CheckCondition(t)
|
||||||
if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
|
if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
|
||||||
if g, err := proc.GetG(t); err == nil {
|
if g, err := proc.GetG(t); err == nil {
|
||||||
@ -148,8 +176,8 @@ func (t *nativeThread) ThreadID() int {
|
|||||||
return t.ID
|
return t.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearBreakpoint clears the specified breakpoint.
|
// clearSoftwareBreakpoint clears the specified breakpoint.
|
||||||
func (t *nativeThread) ClearBreakpoint(bp *proc.Breakpoint) error {
|
func (t *nativeThread) clearSoftwareBreakpoint(bp *proc.Breakpoint) error {
|
||||||
if _, err := t.WriteMemory(bp.Addr, bp.OriginalData); err != nil {
|
if _, err := t.WriteMemory(bp.Addr, bp.OriginalData); err != nil {
|
||||||
return fmt.Errorf("could not clear breakpoint %s", err)
|
return fmt.Errorf("could not clear breakpoint %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,3 +132,15 @@ func (t *nativeThread) ReadMemory(buf []byte, addr uint64) (int, error) {
|
|||||||
func (t *nativeThread) restoreRegisters(sr proc.Registers) error {
|
func (t *nativeThread) restoreRegisters(sr proc.Registers) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -120,3 +120,15 @@ func (t *nativeThread) ReadMemory(data []byte, addr uint64) (n int, err error) {
|
|||||||
t.dbp.execPtraceFunc(func() { n, err = ptraceReadData(t.ID, uintptr(addr), data) })
|
t.dbp.execPtraceFunc(func() { n, err = ptraceReadData(t.ID, uintptr(addr), data) })
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -8,3 +8,15 @@ import (
|
|||||||
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
||||||
return fmt.Errorf("restore regs not supported on i386")
|
return fmt.Errorf("restore regs not supported on i386")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/amd64util"
|
||||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,3 +46,76 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
|||||||
}
|
}
|
||||||
return restoreRegistersErr
|
return restoreRegistersErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const debugRegUserOffset = 848 // offset of debug registers in the user struct, see source/arch/x86/kernel/ptrace.c
|
||||||
|
|
||||||
|
func (t *nativeThread) withDebugRegisters(f func(*amd64util.DebugRegisters) error) error {
|
||||||
|
var err error
|
||||||
|
t.dbp.execPtraceFunc(func() {
|
||||||
|
debugregs := make([]uint64, 8)
|
||||||
|
|
||||||
|
for i := range debugregs {
|
||||||
|
if i == 4 || i == 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _, err = sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_PEEKUSR, uintptr(t.ID), uintptr(debugRegUserOffset+uintptr(i)*unsafe.Sizeof(debugregs[0])), uintptr(unsafe.Pointer(&debugregs[i])), 0, 0)
|
||||||
|
if err != nil && err != syscall.Errno(0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drs := amd64util.NewDebugRegisters(&debugregs[0], &debugregs[1], &debugregs[2], &debugregs[3], &debugregs[6], &debugregs[7])
|
||||||
|
|
||||||
|
err = f(drs)
|
||||||
|
|
||||||
|
if drs.Dirty {
|
||||||
|
for i := range debugregs {
|
||||||
|
if i == 4 || i == 5 {
|
||||||
|
// Linux will return EIO for DR4 and DR5
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _, err = sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_POKEUSR, uintptr(t.ID), uintptr(debugRegUserOffset+uintptr(i)*unsafe.Sizeof(debugregs[0])), uintptr(debugregs[i]), 0, 0)
|
||||||
|
if err != nil && err != syscall.Errno(0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err == syscall.Errno(0) || err == sys.ESRCH {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return t.withDebugRegisters(func(drs *amd64util.DebugRegisters) error {
|
||||||
|
return drs.SetBreakpoint(idx, addr, wtype.Read(), wtype.Write(), wtype.Size())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return t.withDebugRegisters(func(drs *amd64util.DebugRegisters) error {
|
||||||
|
drs.ClearBreakpoint(idx)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
var retbp *proc.Breakpoint
|
||||||
|
err := t.withDebugRegisters(func(drs *amd64util.DebugRegisters) error {
|
||||||
|
ok, idx := drs.GetActiveBreakpoint()
|
||||||
|
if ok {
|
||||||
|
for _, bp := range t.dbp.Breakpoints().M {
|
||||||
|
if bp.WatchType != 0 && bp.HWBreakIndex == idx {
|
||||||
|
retbp = bp
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return retbp, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -42,3 +42,15 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
|||||||
}
|
}
|
||||||
return restoreRegistersErr
|
return restoreRegistersErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -155,3 +155,15 @@ func (t *nativeThread) ReadMemory(buf []byte, addr uint64) (int, error) {
|
|||||||
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
||||||
return _SetThreadContext(t.os.hThread, savedRegs.(*winutil.AMD64Registers).Context)
|
return _SetThreadContext(t.os.hThread, savedRegs.(*winutil.AMD64Registers).Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
||||||
|
return proc.ErrHWBreakUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -5216,3 +5216,94 @@ func TestVariablesWithExternalLinking(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWatchpointsBasic(t *testing.T) {
|
||||||
|
skipOn(t, "not implemented", "windows")
|
||||||
|
skipOn(t, "not implemented", "freebsd")
|
||||||
|
skipOn(t, "not implemented", "darwin")
|
||||||
|
skipOn(t, "not implemented", "386")
|
||||||
|
skipOn(t, "not implemented", "arm64")
|
||||||
|
skipOn(t, "not implemented", "rr")
|
||||||
|
|
||||||
|
withTestProcess("databpeasy", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
setFunctionBreakpoint(p, t, "main.main")
|
||||||
|
assertNoError(p.Continue(), t, "Continue 0")
|
||||||
|
assertLineNumber(p, t, 11, "Continue 0") // Position 0
|
||||||
|
|
||||||
|
scope, err := proc.GoroutineScope(p.CurrentThread())
|
||||||
|
assertNoError(err, t, "GoroutineScope")
|
||||||
|
|
||||||
|
bp, err := p.SetWatchpoint(scope, "globalvar1", proc.WatchWrite, nil)
|
||||||
|
assertNoError(err, t, "SetDataBreakpoint(write-only)")
|
||||||
|
|
||||||
|
assertNoError(p.Continue(), t, "Continue 1")
|
||||||
|
assertLineNumber(p, t, 17, "Continue 1") // Position 1
|
||||||
|
|
||||||
|
p.ClearBreakpoint(bp.Addr)
|
||||||
|
|
||||||
|
assertNoError(p.Continue(), t, "Continue 2")
|
||||||
|
assertLineNumber(p, t, 19, "Continue 2") // Position 2
|
||||||
|
|
||||||
|
_, err = p.SetWatchpoint(scope, "globalvar1", proc.WatchWrite|proc.WatchRead, nil)
|
||||||
|
assertNoError(err, t, "SetDataBreakpoint(read-write)")
|
||||||
|
|
||||||
|
assertNoError(p.Continue(), t, "Continue 3")
|
||||||
|
assertLineNumber(p, t, 20, "Continue 3") // Position 3
|
||||||
|
|
||||||
|
p.ClearBreakpoint(bp.Addr)
|
||||||
|
|
||||||
|
assertNoError(p.Continue(), t, "Continue 4")
|
||||||
|
assertLineNumber(p, t, 25, "Continue 4") // Position 4
|
||||||
|
|
||||||
|
_, err = p.SetWatchpoint(scope, "globalvar1", proc.WatchWrite, nil)
|
||||||
|
assertNoError(err, t, "SetDataBreakpoint(write-only, again)")
|
||||||
|
|
||||||
|
assertNoError(p.Continue(), t, "Continue 5")
|
||||||
|
assertLineNumber(p, t, 33, "Continue 5") // Position 5
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatchpointCounts(t *testing.T) {
|
||||||
|
skipOn(t, "not implemented", "windows")
|
||||||
|
skipOn(t, "not implemented", "freebsd")
|
||||||
|
skipOn(t, "not implemented", "darwin")
|
||||||
|
skipOn(t, "not implemented", "386")
|
||||||
|
skipOn(t, "not implemented", "arm64")
|
||||||
|
skipOn(t, "not implemented", "rr")
|
||||||
|
|
||||||
|
protest.AllowRecording(t)
|
||||||
|
withTestProcess("databpcountstest", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
setFunctionBreakpoint(p, t, "main.main")
|
||||||
|
assertNoError(p.Continue(), t, "Continue 0")
|
||||||
|
|
||||||
|
scope, err := proc.GoroutineScope(p.CurrentThread())
|
||||||
|
assertNoError(err, t, "GoroutineScope")
|
||||||
|
|
||||||
|
bp, err := p.SetWatchpoint(scope, "globalvar1", proc.WatchWrite, nil)
|
||||||
|
assertNoError(err, t, "SetWatchpoint(write-only)")
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := p.Continue(); err != nil {
|
||||||
|
if _, exited := err.(proc.ErrProcessExited); exited {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
assertNoError(err, t, "Continue()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("TotalHitCount: %d", bp.TotalHitCount)
|
||||||
|
if bp.TotalHitCount != 200 {
|
||||||
|
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.TotalHitCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bp.HitCount) != 2 {
|
||||||
|
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.HitCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range bp.HitCount {
|
||||||
|
if v != 100 {
|
||||||
|
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.HitCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -101,6 +101,8 @@ func (sr StopReason) String() string {
|
|||||||
return "next finished"
|
return "next finished"
|
||||||
case StopCallReturned:
|
case StopCallReturned:
|
||||||
return "call returned"
|
return "call returned"
|
||||||
|
case StopWatchpoint:
|
||||||
|
return "watchpoint"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -116,6 +118,7 @@ const (
|
|||||||
StopManual // A manual stop was requested
|
StopManual // A manual stop was requested
|
||||||
StopNextFinished // The next/step/stepout command terminated
|
StopNextFinished // The next/step/stepout command terminated
|
||||||
StopCallReturned // An injected call completed
|
StopCallReturned // An injected call completed
|
||||||
|
StopWatchpoint // The target process hit one or more watchpoints
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewTargetConfig contains the configuration for a new Target object,
|
// NewTargetConfig contains the configuration for a new Target object,
|
||||||
|
|||||||
@ -201,6 +201,9 @@ func (dbp *Target) Continue() error {
|
|||||||
dbp.ClearInternalBreakpoints()
|
dbp.ClearInternalBreakpoints()
|
||||||
}
|
}
|
||||||
dbp.StopReason = StopBreakpoint
|
dbp.StopReason = StopBreakpoint
|
||||||
|
if curbp.Breakpoint.WatchType != 0 {
|
||||||
|
dbp.StopReason = StopWatchpoint
|
||||||
|
}
|
||||||
return conditionErrors(threads)
|
return conditionErrors(threads)
|
||||||
default:
|
default:
|
||||||
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
|
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
|
||||||
@ -413,11 +416,11 @@ func (dbp *Target) StepInstruction() (err error) {
|
|||||||
if ok, err := dbp.Valid(); !ok {
|
if ok, err := dbp.Valid(); !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
thread.Breakpoint().Clear()
|
|
||||||
err = thread.StepInstruction()
|
err = thread.StepInstruction()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
thread.Breakpoint().Clear()
|
||||||
err = thread.SetCurrentBreakpoint(true)
|
err = thread.SetCurrentBreakpoint(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -1938,6 +1938,8 @@ func (s *Server) doCommand(command string, asyncSetupDone chan struct{}) {
|
|||||||
stopped.Body.Reason = "pause"
|
stopped.Body.Reason = "pause"
|
||||||
case proc.StopUnknown: // can happen while stopping
|
case proc.StopUnknown: // can happen while stopping
|
||||||
stopped.Body.Reason = "unknown"
|
stopped.Body.Reason = "unknown"
|
||||||
|
case proc.StopWatchpoint:
|
||||||
|
stopped.Body.Reason = "data breakpoint"
|
||||||
default:
|
default:
|
||||||
stopped.Body.Reason = "breakpoint"
|
stopped.Body.Reason = "breakpoint"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user