Promote breakpoints back up to process
This commit is contained in:
parent
4c95bf7302
commit
c625f09a17
@ -3,6 +3,7 @@
|
|||||||
package proctl
|
package proctl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"debug/gosym"
|
"debug/gosym"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -24,10 +25,32 @@ type DebuggedProcess struct {
|
|||||||
Symbols []elf.Symbol
|
Symbols []elf.Symbol
|
||||||
GoSymTable *gosym.Table
|
GoSymTable *gosym.Table
|
||||||
FrameEntries *frame.FrameDescriptionEntries
|
FrameEntries *frame.FrameDescriptionEntries
|
||||||
|
BreakPoints map[uint64]*BreakPoint
|
||||||
Threads map[int]*ThreadContext
|
Threads map[int]*ThreadContext
|
||||||
CurrentThread *ThreadContext
|
CurrentThread *ThreadContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type BreakPointExistsError struct {
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
addr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bpe BreakPointExistsError) Error() string {
|
||||||
|
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
||||||
|
}
|
||||||
|
|
||||||
func AttachBinary(name string) (*DebuggedProcess, error) {
|
func AttachBinary(name string) (*DebuggedProcess, error) {
|
||||||
proc := exec.Command(name)
|
proc := exec.Command(name)
|
||||||
proc.Stdout = os.Stdout
|
proc.Stdout = os.Stdout
|
||||||
@ -48,8 +71,9 @@ func AttachBinary(name string) (*DebuggedProcess, error) {
|
|||||||
// Returns a new DebuggedProcess struct with sensible defaults.
|
// Returns a new DebuggedProcess struct with sensible defaults.
|
||||||
func NewDebugProcess(pid int) (*DebuggedProcess, error) {
|
func NewDebugProcess(pid int) (*DebuggedProcess, error) {
|
||||||
debuggedProc := DebuggedProcess{
|
debuggedProc := DebuggedProcess{
|
||||||
Pid: pid,
|
Pid: pid,
|
||||||
Threads: make(map[int]*ThreadContext),
|
Threads: make(map[int]*ThreadContext),
|
||||||
|
BreakPoints: make(map[uint64]*BreakPoint),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := debuggedProc.AttachThread(pid)
|
_, err := debuggedProc.AttachThread(pid)
|
||||||
@ -149,10 +173,9 @@ func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tctxt := &ThreadContext{
|
tctxt := &ThreadContext{
|
||||||
Id: tid,
|
Id: tid,
|
||||||
Process: dbp,
|
Process: dbp,
|
||||||
Regs: new(syscall.PtraceRegs),
|
Regs: new(syscall.PtraceRegs),
|
||||||
BreakPoints: make(map[uint64]*BreakPoint),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tid == dbp.Pid {
|
if tid == dbp.Pid {
|
||||||
@ -164,16 +187,67 @@ func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
|||||||
return tctxt, nil
|
return tctxt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets a breakpoint in the running process.
|
||||||
|
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
|
||||||
|
var (
|
||||||
|
int3 = []byte{0xCC}
|
||||||
|
f, l, fn = dbp.GoSymTable.PCToLine(uint64(addr))
|
||||||
|
originalData = make([]byte, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
if fn == nil {
|
||||||
|
return nil, InvalidAddressError{address: addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := syscall.PtracePeekData(dbp.CurrentThread.Id, addr, originalData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Equal(originalData, int3) {
|
||||||
|
return nil, BreakPointExistsError{f, l, addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = syscall.PtracePokeData(dbp.CurrentThread.Id, addr, int3)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
breakpoint := &BreakPoint{
|
||||||
|
FunctionName: fn.Name,
|
||||||
|
File: f,
|
||||||
|
Line: l,
|
||||||
|
Addr: uint64(addr),
|
||||||
|
OriginalData: originalData,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbp.BreakPoints[uint64(addr)] = breakpoint
|
||||||
|
|
||||||
|
return breakpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears a breakpoint.
|
||||||
|
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
|
||||||
|
bp, ok := dbp.BreakPoints[pc]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("No breakpoint currently set for %#v", pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := syscall.PtracePokeData(dbp.CurrentThread.Id, uintptr(bp.Addr), bp.OriginalData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(dbp.BreakPoints, pc)
|
||||||
|
|
||||||
|
return bp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the status of the current main thread context.
|
// Returns the status of the current main thread context.
|
||||||
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
||||||
return dbp.CurrentThread.Status
|
return dbp.CurrentThread.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the breakpoints of the current main thread context.
|
|
||||||
func (dbp *DebuggedProcess) BreakPoints() map[uint64]*BreakPoint {
|
|
||||||
return dbp.CurrentThread.BreakPoints
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finds the executable from /proc/<pid>/exe and then
|
// Finds the executable from /proc/<pid>/exe and then
|
||||||
// uses that to parse the following information:
|
// uses that to parse the following information:
|
||||||
// * Dwarf .debug_frame section
|
// * Dwarf .debug_frame section
|
||||||
@ -257,16 +331,6 @@ func (iae InvalidAddressError) Error() string {
|
|||||||
return fmt.Sprintf("Invalid address %#v\n", iae.address)
|
return fmt.Sprintf("Invalid address %#v\n", iae.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets a breakpoint in the running process.
|
|
||||||
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
|
|
||||||
return dbp.CurrentThread.Break(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clears a breakpoint.
|
|
||||||
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
|
|
||||||
return dbp.CurrentThread.Clear(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
|
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
|
||||||
return dbp.CurrentThread.CurrentPC()
|
return dbp.CurrentThread.CurrentPC()
|
||||||
}
|
}
|
||||||
@ -429,7 +493,7 @@ func wait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus,
|
|||||||
for _, th := range dbp.Threads {
|
for _, th := range dbp.Threads {
|
||||||
if th.Id == pid {
|
if th.Id == pid {
|
||||||
pc, _ := th.CurrentPC()
|
pc, _ := th.CurrentPC()
|
||||||
_, ok := th.BreakPoints[pc-1]
|
_, ok := dbp.BreakPoints[pc-1]
|
||||||
if ok {
|
if ok {
|
||||||
return pid, &status, nil
|
return pid, &status, nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -169,7 +169,7 @@ func TestClearBreakPoint(t *testing.T) {
|
|||||||
t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
|
t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.BreakPoints()) != 0 {
|
if len(p.BreakPoints) != 0 {
|
||||||
t.Fatal("Breakpoint not removed internally")
|
t.Fatal("Breakpoint not removed internally")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -229,7 +229,7 @@ func TestNext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.BreakPoints()) != 1 {
|
if len(p.BreakPoints) != 1 {
|
||||||
t.Fatal("Not all breakpoints were cleaned up")
|
t.Fatal("Not all breakpoints were cleaned up")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -18,22 +18,10 @@ import (
|
|||||||
// ThreadContext represents a single thread of execution in the
|
// ThreadContext represents a single thread of execution in the
|
||||||
// traced program.
|
// traced program.
|
||||||
type ThreadContext struct {
|
type ThreadContext struct {
|
||||||
Id int
|
Id int
|
||||||
Process *DebuggedProcess
|
Process *DebuggedProcess
|
||||||
Status *syscall.WaitStatus
|
Status *syscall.WaitStatus
|
||||||
Regs *syscall.PtraceRegs
|
Regs *syscall.PtraceRegs
|
||||||
BreakPoints map[uint64]*BreakPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
@ -42,16 +30,6 @@ type Variable struct {
|
|||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
type BreakPointExistsError struct {
|
|
||||||
file string
|
|
||||||
line int
|
|
||||||
addr uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bpe BreakPointExistsError) Error() string {
|
|
||||||
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtains register values from the debugged process.
|
// Obtains register values from the debugged process.
|
||||||
func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) {
|
func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) {
|
||||||
err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
|
err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
|
||||||
@ -80,62 +58,6 @@ func (thread *ThreadContext) CurrentPC() (uint64, error) {
|
|||||||
return regs.PC(), nil
|
return regs.PC(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets a breakpoint in the running process.
|
|
||||||
func (thread *ThreadContext) Break(addr uintptr) (*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 := syscall.PtracePeekData(thread.Id, addr, originalData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.Equal(originalData, int3) {
|
|
||||||
return nil, BreakPointExistsError{f, l, addr}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = syscall.PtracePokeData(thread.Id, addr, int3)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
breakpoint := &BreakPoint{
|
|
||||||
FunctionName: fn.Name,
|
|
||||||
File: f,
|
|
||||||
Line: l,
|
|
||||||
Addr: uint64(addr),
|
|
||||||
OriginalData: originalData,
|
|
||||||
}
|
|
||||||
|
|
||||||
thread.BreakPoints[uint64(addr)] = breakpoint
|
|
||||||
|
|
||||||
return breakpoint, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clears a breakpoint.
|
|
||||||
func (thread *ThreadContext) Clear(pc uint64) (*BreakPoint, error) {
|
|
||||||
bp, ok := thread.BreakPoints[pc]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("No breakpoint currently set for %#v", pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := syscall.PtracePokeData(thread.Id, uintptr(bp.Addr), bp.OriginalData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(thread.BreakPoints, pc)
|
|
||||||
|
|
||||||
return bp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (thread *ThreadContext) Continue() error {
|
func (thread *ThreadContext) Continue() error {
|
||||||
// Stepping first will ensure we are able to continue
|
// Stepping first will ensure we are able to continue
|
||||||
// past a breakpoint if that's currently where we are stopped.
|
// past a breakpoint if that's currently where we are stopped.
|
||||||
@ -154,10 +76,10 @@ func (thread *ThreadContext) Step() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bp, ok := thread.BreakPoints[regs.PC()-1]
|
bp, ok := thread.Process.BreakPoints[regs.PC()-1]
|
||||||
if ok {
|
if ok {
|
||||||
// Clear the breakpoint so that we can continue execution.
|
// Clear the breakpoint so that we can continue execution.
|
||||||
_, err = thread.Clear(bp.Addr)
|
_, err = thread.Process.Clear(bp.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -171,7 +93,7 @@ func (thread *ThreadContext) Step() (err error) {
|
|||||||
|
|
||||||
// Restore breakpoint now that we have passed it.
|
// Restore breakpoint now that we have passed it.
|
||||||
defer func() {
|
defer func() {
|
||||||
_, err = thread.Break(uintptr(bp.Addr))
|
_, err = thread.Process.Break(uintptr(bp.Addr))
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +117,7 @@ func (thread *ThreadContext) Next() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := thread.BreakPoints[pc-1]; ok {
|
if _, ok := thread.Process.BreakPoints[pc-1]; ok {
|
||||||
// Decrement the PC to be before
|
// Decrement the PC to be before
|
||||||
// the breakpoint instruction.
|
// the breakpoint instruction.
|
||||||
pc--
|
pc--
|
||||||
@ -251,7 +173,7 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame
|
|||||||
// has not had a chance to modify its' stack
|
// has not had a chance to modify its' stack
|
||||||
// and change our offset.
|
// and change our offset.
|
||||||
addr := thread.Process.ReturnAddressFromOffset(0)
|
addr := thread.Process.ReturnAddressFromOffset(0)
|
||||||
bp, err := thread.Break(uintptr(addr))
|
bp, err := thread.Process.Break(uintptr(addr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BreakPointExistsError); !ok {
|
if _, ok := err.(BreakPointExistsError); !ok {
|
||||||
return err
|
return err
|
||||||
@ -280,14 +202,14 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
|
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
|
||||||
if bp, ok := thread.BreakPoints[pc]; ok {
|
if bp, ok := thread.Process.BreakPoints[pc]; ok {
|
||||||
regs, err := thread.Registers()
|
regs, err := thread.Registers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset program counter to our restored instruction.
|
// Reset program counter to our restored instruction.
|
||||||
bp, err = thread.Clear(bp.Addr)
|
bp, err = thread.Process.Clear(bp.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user