Implement usage of hardware breakpoints
Currently only works for amd64 processors.
This commit is contained in:
parent
c0ae1ee1c6
commit
fbbe9aaa5e
160
proctl/breakpoints_linux_amd64.go
Normal file
160
proctl/breakpoints_linux_amd64.go
Normal file
@ -0,0 +1,160 @@
|
||||
package proctl
|
||||
|
||||
/*
|
||||
#include <stddef.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"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Represents a single breakpoint. Stores information on the break
|
||||
// point including the byte of data that originally was stored at that
|
||||
// address.
|
||||
type BreakPoint struct {
|
||||
FunctionName string
|
||||
File string
|
||||
Line int
|
||||
Addr uint64
|
||||
OriginalData []byte
|
||||
ID int
|
||||
temp bool
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func PtracePokeUser(tid int, off, addr uintptr) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_PTRACE, syscall.PTRACE_POKEUSR, uintptr(tid), uintptr(off), uintptr(addr), 0, 0)
|
||||
if err != syscall.Errno(0) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) BreakpointExists(addr uint64) bool {
|
||||
for _, bp := range dbp.HWBreakPoints {
|
||||
if bp != nil && bp.Addr == addr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if _, ok := dbp.BreakPoints[addr]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64) (*BreakPoint, error) {
|
||||
var f, l, fn = dbp.GoSymTable.PCToLine(uint64(addr))
|
||||
if fn == nil {
|
||||
return nil, InvalidAddressError{address: addr}
|
||||
}
|
||||
if dbp.BreakpointExists(addr) {
|
||||
return nil, BreakPointExistsError{f, l, addr}
|
||||
}
|
||||
// Try and set a hardware breakpoint.
|
||||
for i, v := range dbp.HWBreakPoints {
|
||||
if v == nil {
|
||||
err := setHardwareBreakpoint(i, tid, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not set hardware breakpoint")
|
||||
}
|
||||
breakpointIDCounter++
|
||||
dbp.HWBreakPoints[i] = &BreakPoint{
|
||||
FunctionName: fn.Name,
|
||||
File: f,
|
||||
Line: l,
|
||||
Addr: addr,
|
||||
ID: breakpointIDCounter,
|
||||
}
|
||||
return dbp.HWBreakPoints[i], nil
|
||||
}
|
||||
}
|
||||
// Fall back to software breakpoint. 0xCC is INT 3, software
|
||||
// breakpoint trap interrupt.
|
||||
originalData := make([]byte, 1)
|
||||
if _, err := readMemory(tid, uintptr(addr), originalData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err := writeMemory(tid, uintptr(addr), []byte{0xCC})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
breakpointIDCounter++
|
||||
dbp.BreakPoints[addr] = &BreakPoint{
|
||||
FunctionName: fn.Name,
|
||||
File: f,
|
||||
Line: l,
|
||||
Addr: addr,
|
||||
OriginalData: originalData,
|
||||
ID: breakpointIDCounter,
|
||||
}
|
||||
return dbp.BreakPoints[addr], nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) clearBreakpoint(tid int, addr uint64) (*BreakPoint, error) {
|
||||
// Check for hardware breakpoint
|
||||
for i, bp := range dbp.HWBreakPoints {
|
||||
if bp.Addr == addr {
|
||||
dbp.HWBreakPoints[i] = nil
|
||||
if err := clearHardwareBreakpoint(i, tid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bp, nil
|
||||
}
|
||||
}
|
||||
// Check for software breakpoint
|
||||
if bp, ok := dbp.BreakPoints[addr]; ok {
|
||||
if _, err := writeMemory(tid, uintptr(bp.Addr), bp.OriginalData); err != nil {
|
||||
return nil, fmt.Errorf("could not clear breakpoint %s", err)
|
||||
}
|
||||
delete(dbp.BreakPoints, addr)
|
||||
return bp, nil
|
||||
}
|
||||
return nil, fmt.Errorf("No breakpoint currently set for %#v", addr)
|
||||
}
|
||||
|
||||
// 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 setHardwareBreakpoint(reg, tid int, addr uint64) error {
|
||||
if reg < 0 || reg > 7 {
|
||||
return fmt.Errorf("invalid register value")
|
||||
}
|
||||
|
||||
var (
|
||||
off = uintptr(C.offset(C.int(reg)))
|
||||
dr7 = uintptr(0x1 | C.DR_RW_EXECUTE | C.DR_LEN_8)
|
||||
dr7addr = uintptr(C.offset(C.DR_CONTROL))
|
||||
)
|
||||
|
||||
// Set the debug register `reg` with the address of the
|
||||
// instruction we want to trigger a debug exception.
|
||||
if err := PtracePokeUser(tid, off, uintptr(addr)); err != nil {
|
||||
return err
|
||||
}
|
||||
// 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.
|
||||
return PtracePokeUser(tid, dr7addr, dr7)
|
||||
}
|
||||
@ -25,6 +25,7 @@ type DebuggedProcess struct {
|
||||
Dwarf *dwarf.Data
|
||||
GoSymTable *gosym.Table
|
||||
FrameEntries *frame.FrameDescriptionEntries
|
||||
HWBreakPoints [4]*BreakPoint // May need to change, amd64 supports 4 debug registers
|
||||
BreakPoints map[uint64]*BreakPoint
|
||||
Threads map[int]*ThreadContext
|
||||
CurrentThread *ThreadContext
|
||||
@ -32,29 +33,6 @@ type DebuggedProcess struct {
|
||||
halt bool
|
||||
}
|
||||
|
||||
// Represents a single breakpoint. Stores information on the break
|
||||
// point including the byte of data that originally was stored at that
|
||||
// address.
|
||||
type BreakPoint struct {
|
||||
FunctionName string
|
||||
File string
|
||||
Line int
|
||||
Addr uint64
|
||||
OriginalData []byte
|
||||
ID int
|
||||
temp bool
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type ManualStopError struct{}
|
||||
|
||||
func (mse ManualStopError) Error() string {
|
||||
@ -487,7 +465,16 @@ func handleBreakPoint(dbp *DebuggedProcess, pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check to see if we have hit a user set breakpoint.
|
||||
// Check for hardware breakpoint
|
||||
for _, bp := range dbp.HWBreakPoints {
|
||||
if bp.Addr == pc {
|
||||
if !bp.temp {
|
||||
stopTheWorld(dbp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Check to see if we have hit a software breakpoint.
|
||||
if bp, ok := dbp.BreakPoints[pc-1]; ok {
|
||||
if !bp.temp {
|
||||
stopTheWorld(dbp)
|
||||
|
||||
@ -114,7 +114,7 @@ func TestBreakPoint(t *testing.T) {
|
||||
bp, err := p.Break(sleepyaddr)
|
||||
assertNoError(err, t, "Break()")
|
||||
|
||||
breakpc := bp.Addr + 1
|
||||
breakpc := bp.Addr
|
||||
err = p.Continue()
|
||||
assertNoError(err, t, "Continue()")
|
||||
|
||||
@ -186,11 +186,6 @@ func TestClearBreakPoint(t *testing.T) {
|
||||
bp, err := p.Break(fn.Entry)
|
||||
assertNoError(err, t, "Break()")
|
||||
|
||||
int3, err := dataAtAddr(p.Pid, bp.Addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bp, err = p.Clear(fn.Entry)
|
||||
assertNoError(err, t, "Clear()")
|
||||
|
||||
@ -199,6 +194,7 @@ func TestClearBreakPoint(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
int3 := []byte{0xcc}
|
||||
if bytes.Equal(data, int3) {
|
||||
t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
|
||||
}
|
||||
@ -263,8 +259,14 @@ func TestNext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.BreakPoints) != 1 {
|
||||
t.Fatal("Not all breakpoints were cleaned up", len(p.BreakPoints))
|
||||
p.Clear(pc)
|
||||
if len(p.BreakPoints) != 0 {
|
||||
t.Fatal("Not all breakpoints were cleaned up", len(p.HWBreakPoints))
|
||||
}
|
||||
for _, bp := range p.HWBreakPoints {
|
||||
if bp != nil {
|
||||
t.Fatal("Not all breakpoints were cleaned up", bp.Addr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
package proctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"syscall"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
@ -60,67 +58,22 @@ func (thread *ThreadContext) PrintInfo() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets a software 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
|
||||
// ptrace actions needing the thread to be in a signal-delivery-stop in order
|
||||
// to initiate any ptrace command. Otherwise, it really doesn't matter
|
||||
// as we're only dealing with threads.
|
||||
// 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 (thread *ThreadContext) Break(addr uint64) (*BreakPoint, error) {
|
||||
var (
|
||||
int3 = []byte{0xCC}
|
||||
f, l, fn = thread.Process.GoSymTable.PCToLine(uint64(addr))
|
||||
originalData = make([]byte, 1)
|
||||
)
|
||||
|
||||
if fn == nil {
|
||||
return nil, InvalidAddressError{address: addr}
|
||||
}
|
||||
|
||||
_, err := readMemory(thread.Id, uintptr(addr), originalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Equal(originalData, int3) {
|
||||
return nil, BreakPointExistsError{f, l, addr}
|
||||
}
|
||||
|
||||
_, err = writeMemory(thread.Id, uintptr(addr), int3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
breakpointIDCounter++
|
||||
|
||||
breakpoint := &BreakPoint{
|
||||
FunctionName: fn.Name,
|
||||
File: f,
|
||||
Line: l,
|
||||
Addr: addr,
|
||||
OriginalData: originalData,
|
||||
ID: breakpointIDCounter,
|
||||
}
|
||||
|
||||
thread.Process.BreakPoints[addr] = breakpoint
|
||||
|
||||
return breakpoint, nil
|
||||
return thread.Process.setBreakpoint(thread.Id, addr)
|
||||
}
|
||||
|
||||
// Clears a software breakpoint, and removes it from the process level
|
||||
// break point table.
|
||||
// Clears a breakpoint, and removes it from the process level break point table.
|
||||
func (thread *ThreadContext) Clear(addr uint64) (*BreakPoint, error) {
|
||||
bp, ok := thread.Process.BreakPoints[addr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No breakpoint currently set for %#v", addr)
|
||||
}
|
||||
|
||||
if _, err := writeMemory(thread.Id, uintptr(bp.Addr), bp.OriginalData); err != nil {
|
||||
return nil, fmt.Errorf("could not clear breakpoint %s", err)
|
||||
}
|
||||
|
||||
delete(thread.Process.BreakPoints, addr)
|
||||
|
||||
return bp, nil
|
||||
return thread.Process.clearBreakpoint(thread.Id, addr)
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) Continue() error {
|
||||
@ -270,7 +223,7 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame
|
||||
thread = thread.Process.Threads[wpid]
|
||||
}
|
||||
pc, _ = thread.CurrentPC()
|
||||
if (pc - 1) == bp.Addr {
|
||||
if (pc-1) == bp.Addr || pc == bp.Addr {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -294,19 +247,21 @@ func (thread *ThreadContext) ReturnAddressFromOffset(offset int64) uint64 {
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
|
||||
if bp, ok := thread.Process.BreakPoints[pc]; ok {
|
||||
_, err := thread.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var software bool
|
||||
if _, ok := thread.Process.BreakPoints[pc]; ok {
|
||||
software = true
|
||||
}
|
||||
if _, err := thread.Clear(pc); err != nil {
|
||||
return err
|
||||
}
|
||||
if software {
|
||||
// Reset program counter to our restored instruction.
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return regs.SetPC(thread.Id, bp.Addr)
|
||||
return regs.SetPC(thread.Id, pc)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -35,3 +35,7 @@ func writeMemory(tid int, addr uintptr, data []byte) (int, error) {
|
||||
func readMemory(tid int, addr uintptr, data []byte) (int, error) {
|
||||
return syscall.PtracePeekData(tid, addr, data)
|
||||
}
|
||||
|
||||
func clearHardwareBreakpoint(reg, tid int) error {
|
||||
return setHardwareBreakpoint(reg, tid, 0)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user