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:
|
||||
|
||||
* 386 skipped = 2.7% (4/147)
|
||||
* 386 skipped = 4% (6/149)
|
||||
* 1 broken
|
||||
* 3 broken - cgo stacktraces
|
||||
* arm64 skipped = 2% (3/147)
|
||||
* 2 not implemented
|
||||
* arm64 skipped = 3.4% (5/149)
|
||||
* 1 broken
|
||||
* 1 broken - cgo stacktraces
|
||||
* 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
|
||||
* darwin/lldb skipped = 0.68% (1/147)
|
||||
* darwin/lldb skipped = 0.67% (1/149)
|
||||
* 1 upstream issue
|
||||
* freebsd skipped = 8.2% (12/147)
|
||||
* freebsd skipped = 9.4% (14/149)
|
||||
* 11 broken
|
||||
* 1 not implemented
|
||||
* linux/386/pie skipped = 0.68% (1/147)
|
||||
* 3 not implemented
|
||||
* linux/386/pie skipped = 0.67% (1/149)
|
||||
* 1 broken
|
||||
* pie skipped = 0.68% (1/147)
|
||||
* pie skipped = 0.67% (1/149)
|
||||
* 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
|
||||
* 2 not implemented
|
||||
* 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"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/parser"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -34,6 +35,9 @@ type Breakpoint struct {
|
||||
Name string // User defined name of the 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
|
||||
// stepping).
|
||||
// A single breakpoint can be both a UserBreakpoint and some kind of
|
||||
@ -93,6 +97,36 @@ const (
|
||||
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 (%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
|
||||
// break point table.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -270,8 +347,25 @@ func (t *Target) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr)
|
||||
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,
|
||||
@ -372,6 +466,16 @@ func (bpmap *BreakpointMap) HasInternalBreakpoints() bool {
|
||||
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
|
||||
|
@ -1209,6 +1209,9 @@ func (p *gdbProcess) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -114,6 +114,18 @@ func (t *nativeThread) restoreRegisters(sr proc.Registers) error {
|
||||
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
|
||||
// the operating system level.
|
||||
func (t *nativeThread) Stopped() bool {
|
||||
|
@ -206,6 +206,16 @@ func (dbp *nativeProcess) CheckAndClearManualStopRequest() bool {
|
||||
}
|
||||
|
||||
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())
|
||||
_, err := dbp.memthread.ReadMemory(bp.OriginalData, bp.Addr)
|
||||
if err != nil {
|
||||
@ -215,7 +225,17 @@ func (dbp *nativeProcess) WriteBreakpoint(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.
|
||||
|
@ -268,6 +268,14 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error)
|
||||
if dbp.memthread == nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,17 @@ func (t *nativeThread) StepInstruction() (err error) {
|
||||
defer func() {
|
||||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -61,7 +72,7 @@ func (t *nativeThread) StepInstruction() (err error) {
|
||||
bp, ok := t.dbp.FindBreakpoint(pc, false)
|
||||
if ok {
|
||||
// Clear the breakpoint so that we can continue execution.
|
||||
err = t.ClearBreakpoint(bp)
|
||||
err = t.clearSoftwareBreakpoint(bp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -109,23 +120,40 @@ func (t *nativeThread) Common() *proc.CommonThread {
|
||||
// thread is stopped at as CurrentBreakpoint on the thread struct.
|
||||
func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error {
|
||||
t.CurrentBreakpoint.Clear()
|
||||
pc, err := t.PC()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
var bp *proc.Breakpoint
|
||||
|
||||
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
|
||||
// of PC after being executed we should look for breakpoints
|
||||
// with bp.Addr == PC and there is no need to call SetPC
|
||||
// after finding one.
|
||||
adjustPC = adjustPC && t.BinInfo().Arch.BreakInstrMovesPC()
|
||||
// If the breakpoint instruction does not change the value
|
||||
// of PC after being executed we should look for breakpoints
|
||||
// with bp.Addr == PC and there is no need to call SetPC
|
||||
// after finding one.
|
||||
adjustPC = adjustPC && t.BinInfo().Arch.BreakInstrMovesPC()
|
||||
|
||||
if bp, ok := t.dbp.FindBreakpoint(pc, adjustPC); ok {
|
||||
if adjustPC {
|
||||
if err = t.setPC(bp.Addr); err != nil {
|
||||
return err
|
||||
var ok bool
|
||||
bp, ok = t.dbp.FindBreakpoint(pc, adjustPC)
|
||||
if ok {
|
||||
if adjustPC {
|
||||
if err = t.setPC(bp.Addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bp != nil {
|
||||
t.CurrentBreakpoint = bp.CheckCondition(t)
|
||||
if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
|
||||
if g, err := proc.GetG(t); err == nil {
|
||||
@ -148,8 +176,8 @@ func (t *nativeThread) ThreadID() int {
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// ClearBreakpoint clears the specified breakpoint.
|
||||
func (t *nativeThread) ClearBreakpoint(bp *proc.Breakpoint) error {
|
||||
// clearSoftwareBreakpoint clears the specified breakpoint.
|
||||
func (t *nativeThread) clearSoftwareBreakpoint(bp *proc.Breakpoint) error {
|
||||
if _, err := t.WriteMemory(bp.Addr, bp.OriginalData); err != nil {
|
||||
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 {
|
||||
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) })
|
||||
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 {
|
||||
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"
|
||||
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/amd64util"
|
||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||
)
|
||||
|
||||
@ -45,3 +46,76 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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"
|
||||
case StopCallReturned:
|
||||
return "call returned"
|
||||
case StopWatchpoint:
|
||||
return "watchpoint"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@ -116,6 +118,7 @@ const (
|
||||
StopManual // A manual stop was requested
|
||||
StopNextFinished // The next/step/stepout command terminated
|
||||
StopCallReturned // An injected call completed
|
||||
StopWatchpoint // The target process hit one or more watchpoints
|
||||
)
|
||||
|
||||
// NewTargetConfig contains the configuration for a new Target object,
|
||||
|
@ -201,6 +201,9 @@ func (dbp *Target) Continue() error {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
}
|
||||
dbp.StopReason = StopBreakpoint
|
||||
if curbp.Breakpoint.WatchType != 0 {
|
||||
dbp.StopReason = StopWatchpoint
|
||||
}
|
||||
return conditionErrors(threads)
|
||||
default:
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
thread.Breakpoint().Clear()
|
||||
err = thread.StepInstruction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
thread.Breakpoint().Clear()
|
||||
err = thread.SetCurrentBreakpoint(true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1938,6 +1938,8 @@ func (s *Server) doCommand(command string, asyncSetupDone chan struct{}) {
|
||||
stopped.Body.Reason = "pause"
|
||||
case proc.StopUnknown: // can happen while stopping
|
||||
stopped.Body.Reason = "unknown"
|
||||
case proc.StopWatchpoint:
|
||||
stopped.Body.Reason = "data breakpoint"
|
||||
default:
|
||||
stopped.Body.Reason = "breakpoint"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user