breakpoints_linux_amd64.go 4.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
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")
			}
80
			dbp.breakpointIDCounter++
81 82 83 84 85
			dbp.HWBreakPoints[i] = &BreakPoint{
				FunctionName: fn.Name,
				File:         f,
				Line:         l,
				Addr:         addr,
86
				ID:           dbp.breakpointIDCounter,
87 88 89 90 91 92 93 94 95 96 97 98 99 100
			}
			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
	}
101
	dbp.breakpointIDCounter++
102 103 104 105 106 107
	dbp.BreakPoints[addr] = &BreakPoint{
		FunctionName: fn.Name,
		File:         f,
		Line:         l,
		Addr:         addr,
		OriginalData: originalData,
108
		ID:           dbp.breakpointIDCounter,
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
	}
	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)
}