Implement usage of hardware breakpoints

Currently only works for amd64 processors.
This commit is contained in:
Derek Parker 2015-01-10 17:33:06 -06:00
parent c0ae1ee1c6
commit fbbe9aaa5e
5 changed files with 206 additions and 98 deletions

@ -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)
}