mirror of
https://github.com/go-delve/delve.git
synced 2025-10-28 04:35:19 +08:00
proc: Remove hardware assisted breakpoints
Only use software breakpoints for now. The reasoning is because it complicates the code without justification, and is only supported on Linux. Eventually, once watchpoints are properly implemented we will revive some of this code. Also, if it is ever necessary to actually set a hw breakpoint we can revive that code as well. All future versions of this code will include support for OSX before being merged back in.
This commit is contained in:
@ -1,9 +1,6 @@
|
|||||||
package proc
|
package proc
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Represents a single breakpoint. Stores information on the break
|
// Represents a single breakpoint. Stores information on the break
|
||||||
// point including the byte of data that originally was stored at that
|
// point including the byte of data that originally was stored at that
|
||||||
@ -24,10 +21,6 @@ type Breakpoint struct {
|
|||||||
Stacktrace int // Number of stack frames to retrieve
|
Stacktrace int // Number of stack frames to retrieve
|
||||||
Goroutine bool // Retrieve goroutine information
|
Goroutine bool // Retrieve goroutine information
|
||||||
Variables []string // Variables to evaluate
|
Variables []string // Variables to evaluate
|
||||||
|
|
||||||
hardware bool // Breakpoint using CPU debug registers.
|
|
||||||
reg int // If hardware breakpoint, what debug register it belongs to.
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *Breakpoint) String() string {
|
func (bp *Breakpoint) String() string {
|
||||||
@ -37,12 +30,6 @@ func (bp *Breakpoint) String() string {
|
|||||||
// Clear this breakpoint appropriately depending on whether it is a
|
// Clear this breakpoint appropriately depending on whether it is a
|
||||||
// hardware or software breakpoint.
|
// hardware or software breakpoint.
|
||||||
func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
|
func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
|
||||||
if bp.hardware {
|
|
||||||
if err := thread.dbp.clearHardwareBreakpoint(bp.reg, thread.Id); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bp, nil
|
|
||||||
}
|
|
||||||
if _, err := thread.writeMemory(uintptr(bp.Addr), bp.OriginalData); err != nil {
|
if _, err := thread.writeMemory(uintptr(bp.Addr), bp.OriginalData); err != nil {
|
||||||
return nil, fmt.Errorf("could not clear breakpoint %s", err)
|
return nil, fmt.Errorf("could not clear breakpoint %s", err)
|
||||||
}
|
}
|
||||||
@ -97,34 +84,6 @@ func (dbp *Process) setBreakpoint(tid int, addr uint64, temp bool) (*Breakpoint,
|
|||||||
newBreakpoint.ID = dbp.breakpointIDCounter
|
newBreakpoint.ID = dbp.breakpointIDCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and set a hardware breakpoint.
|
|
||||||
for i, used := range dbp.arch.HardwareBreakpointUsage() {
|
|
||||||
if runtime.GOOS == "darwin" { // TODO(dp): Implement hardware breakpoints on OSX.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if used {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for tid, t := range dbp.Threads {
|
|
||||||
if t.running {
|
|
||||||
err := t.Halt()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer t.Continue()
|
|
||||||
}
|
|
||||||
if err := dbp.setHardwareBreakpoint(i, tid, addr); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not set hardware breakpoint on thread %d: %s", t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbp.arch.SetHardwareBreakpointUsage(i, true)
|
|
||||||
newBreakpoint.reg = i
|
|
||||||
newBreakpoint.hardware = true
|
|
||||||
dbp.Breakpoints[addr] = newBreakpoint
|
|
||||||
return dbp.Breakpoints[addr], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to software breakpoint.
|
|
||||||
thread := dbp.Threads[tid]
|
thread := dbp.Threads[tid]
|
||||||
originalData, err := thread.readMemory(uintptr(addr), dbp.arch.BreakpointSize())
|
originalData, err := thread.readMemory(uintptr(addr), dbp.arch.BreakpointSize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
package proc
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// TODO(darwin)
|
|
||||||
func (dbp *Process) setHardwareBreakpoint(reg, tid int, addr uint64) error {
|
|
||||||
return fmt.Errorf("not implemented on darwin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(darwin)
|
|
||||||
func (dbp *Process) clearHardwareBreakpoint(reg, tid int) error {
|
|
||||||
return fmt.Errorf("not implemented on darwin")
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package proc
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/user.h>
|
|
||||||
#include <sys/debugreg.h>
|
|
||||||
|
|
||||||
// Exposes C macro `offsetof` which is needed for getting
|
|
||||||
// the offset of the debug register we want, and passing
|
|
||||||
// that offset to PTRACE_POKE_USER.
|
|
||||||
int offset(int reg) {
|
|
||||||
return offsetof(struct user, u_debugreg[reg]);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Sets a hardware breakpoint by setting the contents of the
|
|
||||||
// debug register `reg` with the address of the instruction
|
|
||||||
// that we want to break at. There are only 4 debug registers
|
|
||||||
// DR0-DR3. Debug register 7 is the control register.
|
|
||||||
func (dbp *Process) setHardwareBreakpoint(reg, tid int, addr uint64) error {
|
|
||||||
if reg < 0 || reg > 3 {
|
|
||||||
return fmt.Errorf("invalid debug register value")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
dr7off = uintptr(C.offset(C.DR_CONTROL))
|
|
||||||
drxoff = uintptr(C.offset(C.int(reg)))
|
|
||||||
drxmask = uintptr((((1 << C.DR_CONTROL_SIZE) - 1) << uintptr(reg*C.DR_CONTROL_SIZE)) | (((1 << C.DR_ENABLE_SIZE) - 1) << uintptr(reg*C.DR_ENABLE_SIZE)))
|
|
||||||
drxenable = uintptr(0x1) << uintptr(reg*C.DR_ENABLE_SIZE)
|
|
||||||
drxctl = uintptr(C.DR_RW_EXECUTE|C.DR_LEN_1) << uintptr(reg*C.DR_CONTROL_SIZE)
|
|
||||||
dr7 uintptr
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get current state
|
|
||||||
var err error
|
|
||||||
dbp.execPtraceFunc(func() { dr7, err = PtracePeekUser(tid, dr7off) })
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If addr == 0 we are expected to disable the breakpoint
|
|
||||||
if addr == 0 {
|
|
||||||
dr7 &= ^drxmask
|
|
||||||
dbp.execPtraceFunc(func() { err = PtracePokeUser(tid, dr7off, dr7) })
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the debug register `reg` with the address of the
|
|
||||||
// instruction we want to trigger a debug exception.
|
|
||||||
dbp.execPtraceFunc(func() { err = PtracePokeUser(tid, drxoff, uintptr(addr)) })
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear dr`reg` flags
|
|
||||||
dr7 &= ^drxmask
|
|
||||||
// Enable dr`reg`
|
|
||||||
dr7 |= (drxctl << C.DR_CONTROL_SHIFT) | drxenable
|
|
||||||
|
|
||||||
// Set the debug control register. This
|
|
||||||
// instructs the cpu to raise a debug
|
|
||||||
// exception when hitting the address of
|
|
||||||
// an instruction stored in dr0-dr3.
|
|
||||||
dbp.execPtraceFunc(func() { err = PtracePokeUser(tid, dr7off, dr7) })
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clears a hardware breakpoint. Essentially sets
|
|
||||||
// the debug reg to 0 and clears the control register
|
|
||||||
// flags for that reg.
|
|
||||||
func (dbp *Process) clearHardwareBreakpoint(reg, tid int) error {
|
|
||||||
return dbp.setHardwareBreakpoint(reg, tid, 0)
|
|
||||||
}
|
|
||||||
33
proc/proc.go
33
proc/proc.go
@ -25,7 +25,7 @@ type Process struct {
|
|||||||
Pid int // Process Pid
|
Pid int // Process Pid
|
||||||
Process *os.Process // Pointer to process struct for the actual process we are debugging
|
Process *os.Process // Pointer to process struct for the actual process we are debugging
|
||||||
|
|
||||||
// Breakpoint table, hold information on software / hardware breakpoints.
|
// Breakpoint table, holds information on breakpoints.
|
||||||
// Maps instruction address to Breakpoint struct.
|
// Maps instruction address to Breakpoint struct.
|
||||||
Breakpoints map[uint64]*Breakpoint
|
Breakpoints map[uint64]*Breakpoint
|
||||||
|
|
||||||
@ -205,12 +205,6 @@ func (dbp *Process) RequestManualStop() error {
|
|||||||
// Sets a breakpoint at addr, and stores it in the process wide
|
// Sets a breakpoint at addr, and stores it in the process wide
|
||||||
// break point table. Setting a break point must be thread specific due to
|
// break point table. Setting a break point must be thread specific due to
|
||||||
// ptrace actions needing the thread to be in a signal-delivery-stop.
|
// ptrace actions needing the thread to be in a signal-delivery-stop.
|
||||||
//
|
|
||||||
// Depending on hardware support, Delve will choose to either
|
|
||||||
// set a hardware or software breakpoint. Essentially, if the
|
|
||||||
// hardware supports it, and there are free debug registers, Delve
|
|
||||||
// will set a hardware breakpoint. Otherwise we fall back to software
|
|
||||||
// breakpoints, which are a bit more work for us.
|
|
||||||
func (dbp *Process) SetBreakpoint(addr uint64) (*Breakpoint, error) {
|
func (dbp *Process) SetBreakpoint(addr uint64) (*Breakpoint, error) {
|
||||||
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, false)
|
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, false)
|
||||||
}
|
}
|
||||||
@ -221,27 +215,16 @@ func (dbp *Process) SetTempBreakpoint(addr uint64) (*Breakpoint, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clears a breakpoint.
|
// Clears a breakpoint.
|
||||||
//
|
|
||||||
// If it is a hardware assisted breakpoint, iterate through all threads
|
|
||||||
// and clear the debug register. Otherwise, restore original instruction.
|
|
||||||
func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
|
func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
|
||||||
bp, ok := dbp.Breakpoints[addr]
|
bp, ok := dbp.FindBreakpoint(addr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, NoBreakpointError{addr: addr}
|
return nil, NoBreakpointError{addr: addr}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, thread := range dbp.Threads {
|
if _, err := bp.Clear(dbp.CurrentThread); err != nil {
|
||||||
if _, err := bp.Clear(thread); err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !bp.hardware {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bp.hardware {
|
|
||||||
dbp.arch.SetHardwareBreakpointUsage(bp.reg, false)
|
|
||||||
}
|
|
||||||
delete(dbp.Breakpoints, addr)
|
delete(dbp.Breakpoints, addr)
|
||||||
|
|
||||||
return bp, nil
|
return bp, nil
|
||||||
@ -566,15 +549,11 @@ func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) {
|
|||||||
|
|
||||||
// Finds the breakpoint for the given pc.
|
// Finds the breakpoint for the given pc.
|
||||||
func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
|
func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
|
||||||
// Check for software breakpoint. PC will be at
|
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
|
||||||
// breakpoint instruction + size of breakpoint.
|
|
||||||
if bp, ok := dbp.Breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok {
|
if bp, ok := dbp.Breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok {
|
||||||
return bp, true
|
return bp, true
|
||||||
}
|
}
|
||||||
// Check for hardware breakpoint. PC will equal
|
// Directly use addr to lookup breakpoint.
|
||||||
// the breakpoint address since the CPU will stop
|
|
||||||
// the process without executing the instruction at
|
|
||||||
// this address.
|
|
||||||
if bp, ok := dbp.Breakpoints[pc]; ok {
|
if bp, ok := dbp.Breakpoints[pc]; ok {
|
||||||
return bp, true
|
return bp, true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -267,15 +267,6 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Set all hardware breakpoints on the new thread.
|
|
||||||
for _, bp := range dbp.Breakpoints {
|
|
||||||
if !bp.hardware {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = dbp.setHardwareBreakpoint(bp.reg, th.Id, bp.Addr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = th.Continue(); err != nil {
|
if err = th.Continue(); err != nil {
|
||||||
return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
|
return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -727,7 +727,7 @@ func TestContinueMulti(t *testing.T) {
|
|||||||
sayhiCount := 0
|
sayhiCount := 0
|
||||||
for {
|
for {
|
||||||
err := p.Continue()
|
err := p.Continue()
|
||||||
if p.exited {
|
if p.Exited() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
assertNoError(err, t, "Continue()")
|
assertNoError(err, t, "Continue()")
|
||||||
|
|||||||
@ -50,11 +50,9 @@ func (thread *Thread) Continue() error {
|
|||||||
}
|
}
|
||||||
// Check whether we are stopped at a breakpoint, and
|
// Check whether we are stopped at a breakpoint, and
|
||||||
// if so, single step over it before continuing.
|
// if so, single step over it before continuing.
|
||||||
if bp, ok := thread.dbp.FindBreakpoint(pc); ok {
|
if _, ok := thread.dbp.FindBreakpoint(pc); ok {
|
||||||
if !bp.hardware {
|
if err := thread.Step(); err != nil {
|
||||||
if err := thread.Step(); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return thread.resume()
|
return thread.resume()
|
||||||
@ -78,7 +76,7 @@ func (thread *Thread) Step() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bp, ok := thread.dbp.Breakpoints[pc]
|
bp, ok := thread.dbp.FindBreakpoint(pc)
|
||||||
if ok {
|
if ok {
|
||||||
// Clear the breakpoint so that we can continue execution.
|
// Clear the breakpoint so that we can continue execution.
|
||||||
_, err = bp.Clear(thread)
|
_, err = bp.Clear(thread)
|
||||||
@ -88,11 +86,7 @@ func (thread *Thread) Step() (err error) {
|
|||||||
|
|
||||||
// Restore breakpoint now that we have passed it.
|
// Restore breakpoint now that we have passed it.
|
||||||
defer func() {
|
defer func() {
|
||||||
if bp.hardware {
|
err = thread.dbp.writeSoftwareBreakpoint(thread, bp.Addr)
|
||||||
err = thread.dbp.setHardwareBreakpoint(bp.reg, thread.Id, bp.Addr)
|
|
||||||
} else {
|
|
||||||
err = thread.dbp.writeSoftwareBreakpoint(thread, bp.Addr)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user