breakpoints_linux_amd64.go 5.8 KB
Newer Older
1 2 3 4
package proctl

/*
#include <stddef.h>
5
#include <sys/types.h>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#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"
M
Michael Gehring 已提交
21
	"unsafe"
22 23

	sys "golang.org/x/sys/unix"
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
)

// 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
}

D
Derek Parker 已提交
39 40
// Returned when trying to set a breakpoint at
// an address that already has a breakpoint set for it.
41 42 43 44 45 46 47 48 49 50
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)
}

D
Derek Parker 已提交
51 52 53 54 55 56 57 58 59 60
// InvalidAddressError represents the result of
// attempting to set a breakpoint at an invalid address.
type InvalidAddressError struct {
	address uint64
}

func (iae InvalidAddressError) Error() string {
	return fmt.Sprintf("Invalid address %#v\n", iae.address)
}

61
func PtracePokeUser(tid int, off, addr uintptr) error {
P
Paul Sbarra 已提交
62
	_, _, err := sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_POKEUSR, uintptr(tid), uintptr(off), uintptr(addr), 0, 0)
63 64 65 66 67 68
	if err != syscall.Errno(0) {
		return err
	}
	return nil
}

M
Michael Gehring 已提交
69
func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
M
Michael Gehring 已提交
70 71
	var val uintptr
	_, _, err := syscall.Syscall6(syscall.SYS_PTRACE, syscall.PTRACE_PEEKUSR, uintptr(tid), uintptr(off), uintptr(unsafe.Pointer(&val)), 0, 0)
M
Michael Gehring 已提交
72 73 74
	if err != syscall.Errno(0) {
		return 0, err
	}
M
Michael Gehring 已提交
75
	return val, nil
M
Michael Gehring 已提交
76 77
}

D
Derek Parker 已提交
78
// Returns whether or not a breakpoint has been set for the given address.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
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 {
D
Derek Parker 已提交
102
			if err := setHardwareBreakpoint(i, tid, addr); err != nil {
103
				return nil, fmt.Errorf("could not set hardware breakpoint: %v", err)
104
			}
D
Derek Parker 已提交
105
			dbp.HWBreakPoints[i] = dbp.newBreakpoint(fn.Name, f, l, addr, nil)
106 107 108 109 110 111 112 113 114 115 116 117 118
			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
	}
D
Derek Parker 已提交
119
	dbp.BreakPoints[addr] = dbp.newBreakpoint(fn.Name, f, l, addr, originalData)
120 121 122 123 124 125
	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 {
126 127 128
		if bp == nil {
			continue
		}
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
		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)
}

D
Derek Parker 已提交
148 149 150 151 152 153 154 155 156 157 158 159
func (dbp *DebuggedProcess) newBreakpoint(fn, f string, l int, addr uint64, data []byte) *BreakPoint {
	dbp.breakpointIDCounter++
	return &BreakPoint{
		FunctionName: fn,
		File:         f,
		Line:         l,
		Addr:         addr,
		OriginalData: data,
		ID:           dbp.breakpointIDCounter,
	}
}

160 161 162 163 164
// 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 {
M
Michael Gehring 已提交
165
	if reg < 0 || reg > 3 {
166 167 168 169
		return fmt.Errorf("invalid register value")
	}

	var (
M
Michael Gehring 已提交
170 171 172 173 174
		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)
175 176
	)

M
Michael Gehring 已提交
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
	// Get current state
	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
		return PtracePokeUser(tid, dr7off, dr7)
	}

	// Error out if dr`reg` is already used
	if dr7&(0x3<<uint(reg*C.DR_ENABLE_SIZE)) != 0 {
		return fmt.Errorf("dr%d already enabled", reg)
	}

194 195
	// Set the debug register `reg` with the address of the
	// instruction we want to trigger a debug exception.
M
Michael Gehring 已提交
196
	if err := PtracePokeUser(tid, drxoff, uintptr(addr)); err != nil {
197 198
		return err
	}
M
Michael Gehring 已提交
199 200 201 202

	// Clear dr`reg` flags
	dr7 &= ^drxmask
	// Enable dr`reg`
203
	dr7 |= (drxctl << C.DR_CONTROL_SHIFT) | drxenable
M
Michael Gehring 已提交
204

205 206 207 208
	// 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.
M
Michael Gehring 已提交
209
	return PtracePokeUser(tid, dr7off, dr7)
210
}