diff --git a/proctl/breakpoints_linux_amd64.go b/proctl/breakpoints_linux_amd64.go new file mode 100644 index 00000000..f78bf7fe --- /dev/null +++ b/proctl/breakpoints_linux_amd64.go @@ -0,0 +1,160 @@ +package proctl + +/* +#include +#include +#include + +// 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) +} diff --git a/proctl/proctl.go b/proctl/proctl.go index 5e5edc16..0124559b 100644 --- a/proctl/proctl.go +++ b/proctl/proctl.go @@ -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) diff --git a/proctl/proctl_test.go b/proctl/proctl_test.go index a85758e8..f6701a66 100644 --- a/proctl/proctl_test.go +++ b/proctl/proctl_test.go @@ -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) + } } }) } diff --git a/proctl/threads.go b/proctl/threads.go index 2a9420ca..543410da 100644 --- a/proctl/threads.go +++ b/proctl/threads.go @@ -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 diff --git a/proctl/threads_linux_amd64.go b/proctl/threads_linux_amd64.go index a08856df..618689d0 100644 --- a/proctl/threads_linux_amd64.go +++ b/proctl/threads_linux_amd64.go @@ -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) +}