Promote breakpoints back up to process
This commit is contained in:
parent
4c95bf7302
commit
c625f09a17
@ -3,6 +3,7 @@
|
||||
package proctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/gosym"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
@ -24,10 +25,32 @@ type DebuggedProcess struct {
|
||||
Symbols []elf.Symbol
|
||||
GoSymTable *gosym.Table
|
||||
FrameEntries *frame.FrameDescriptionEntries
|
||||
BreakPoints map[uint64]*BreakPoint
|
||||
Threads map[int]*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) {
|
||||
proc := exec.Command(name)
|
||||
proc.Stdout = os.Stdout
|
||||
@ -48,8 +71,9 @@ func AttachBinary(name string) (*DebuggedProcess, error) {
|
||||
// Returns a new DebuggedProcess struct with sensible defaults.
|
||||
func NewDebugProcess(pid int) (*DebuggedProcess, error) {
|
||||
debuggedProc := DebuggedProcess{
|
||||
Pid: pid,
|
||||
Threads: make(map[int]*ThreadContext),
|
||||
Pid: pid,
|
||||
Threads: make(map[int]*ThreadContext),
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
}
|
||||
|
||||
_, err := debuggedProc.AttachThread(pid)
|
||||
@ -149,10 +173,9 @@ func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
||||
}
|
||||
|
||||
tctxt := &ThreadContext{
|
||||
Id: tid,
|
||||
Process: dbp,
|
||||
Regs: new(syscall.PtraceRegs),
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
Id: tid,
|
||||
Process: dbp,
|
||||
Regs: new(syscall.PtraceRegs),
|
||||
}
|
||||
|
||||
if tid == dbp.Pid {
|
||||
@ -164,16 +187,67 @@ func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
||||
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.
|
||||
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
||||
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
|
||||
// uses that to parse the following information:
|
||||
// * Dwarf .debug_frame section
|
||||
@ -257,16 +331,6 @@ func (iae InvalidAddressError) Error() string {
|
||||
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) {
|
||||
return dbp.CurrentThread.CurrentPC()
|
||||
}
|
||||
@ -429,7 +493,7 @@ func wait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus,
|
||||
for _, th := range dbp.Threads {
|
||||
if th.Id == pid {
|
||||
pc, _ := th.CurrentPC()
|
||||
_, ok := th.BreakPoints[pc-1]
|
||||
_, ok := dbp.BreakPoints[pc-1]
|
||||
if ok {
|
||||
return pid, &status, nil
|
||||
} else {
|
||||
|
@ -169,7 +169,7 @@ func TestClearBreakPoint(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
})
|
||||
@ -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")
|
||||
}
|
||||
})
|
||||
|
@ -18,22 +18,10 @@ import (
|
||||
// ThreadContext represents a single thread of execution in the
|
||||
// traced program.
|
||||
type ThreadContext struct {
|
||||
Id int
|
||||
Process *DebuggedProcess
|
||||
Status *syscall.WaitStatus
|
||||
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
|
||||
Id int
|
||||
Process *DebuggedProcess
|
||||
Status *syscall.WaitStatus
|
||||
Regs *syscall.PtraceRegs
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
@ -42,16 +30,6 @@ type Variable struct {
|
||||
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.
|
||||
func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) {
|
||||
err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
|
||||
@ -80,62 +58,6 @@ func (thread *ThreadContext) CurrentPC() (uint64, error) {
|
||||
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 {
|
||||
// Stepping first will ensure we are able to continue
|
||||
// past a breakpoint if that's currently where we are stopped.
|
||||
@ -154,10 +76,10 @@ func (thread *ThreadContext) Step() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
bp, ok := thread.BreakPoints[regs.PC()-1]
|
||||
bp, ok := thread.Process.BreakPoints[regs.PC()-1]
|
||||
if ok {
|
||||
// Clear the breakpoint so that we can continue execution.
|
||||
_, err = thread.Clear(bp.Addr)
|
||||
_, err = thread.Process.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -171,7 +93,7 @@ func (thread *ThreadContext) Step() (err error) {
|
||||
|
||||
// Restore breakpoint now that we have passed it.
|
||||
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
|
||||
}
|
||||
|
||||
if _, ok := thread.BreakPoints[pc-1]; ok {
|
||||
if _, ok := thread.Process.BreakPoints[pc-1]; ok {
|
||||
// Decrement the PC to be before
|
||||
// the breakpoint instruction.
|
||||
pc--
|
||||
@ -251,7 +173,7 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame
|
||||
// has not had a chance to modify its' stack
|
||||
// and change our offset.
|
||||
addr := thread.Process.ReturnAddressFromOffset(0)
|
||||
bp, err := thread.Break(uintptr(addr))
|
||||
bp, err := thread.Process.Break(uintptr(addr))
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakPointExistsError); !ok {
|
||||
return err
|
||||
@ -280,14 +202,14 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset program counter to our restored instruction.
|
||||
bp, err = thread.Clear(bp.Addr)
|
||||
bp, err = thread.Process.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user