delve/pkg/proc/native/proc.go
aarzilli 1ced7c3a60 proc: next should not skip lines with conditional bps
Conditional breakpoints with unmet conditions would cause next and step
to skip the line.

This breakpoint changes the Kind field of proc.Breakpoint from a single
value to a bit field, each breakpoint object can represent
simultaneously a user breakpoint and one internal breakpoint (of which
we have several different kinds).

The breakpoint condition for internal breakpoints is stored in the new
internalCond field of proc.Breakpoint so that it will not conflict with
user specified conditions.

The breakpoint setting code is changed to allow overlapping one
internal breakpoint on a user breakpoint, or a user breakpoint on an
existing internal breakpoint. All other combinations are rejected. The
breakpoint clearing code is changed to clear the UserBreakpoint bit and
only remove the phisical breakpoint if no other bits are set in the
Kind field. ClearInternalBreakpoints does the same thing but clearing
all bits that aren't the UserBreakpoint bit.

Fixes #844
2017-11-20 11:25:35 -08:00

428 lines
11 KiB
Go

package native
import (
"fmt"
"go/ast"
"runtime"
"sync"
"github.com/derekparker/delve/pkg/proc"
)
// Process represents all of the information the debugger
// is holding onto regarding the process we are debugging.
type Process struct {
bi proc.BinaryInfo
pid int // Process Pid
// Breakpoint table, holds information on breakpoints.
// Maps instruction address to Breakpoint struct.
breakpoints proc.BreakpointMap
// List of threads mapped as such: pid -> *Thread
threads map[int]*Thread
// Active thread
currentThread *Thread
// Goroutine that will be used by default to set breakpoint, eval variables, etc...
// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
selectedGoroutine *proc.G
allGCache []*proc.G
os *OSProcessDetails
firstStart bool
haltMu sync.Mutex
halt bool
resumeChan chan<- struct{}
exited bool
ptraceChan chan func()
ptraceDoneChan chan interface{}
childProcess bool // this process was launched, not attached to
manualStopRequested bool
}
// New returns an initialized Process struct. Before returning,
// it will also launch a goroutine in order to handle ptrace(2)
// functions. For more information, see the documentation on
// `handlePtraceFuncs`.
func New(pid int) *Process {
dbp := &Process{
pid: pid,
threads: make(map[int]*Thread),
breakpoints: proc.NewBreakpointMap(),
firstStart: true,
os: new(OSProcessDetails),
ptraceChan: make(chan func()),
ptraceDoneChan: make(chan interface{}),
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
}
go dbp.handlePtraceFuncs()
return dbp
}
func (dbp *Process) BinInfo() *proc.BinaryInfo {
return &dbp.bi
}
func (dbp *Process) Recorded() (bool, string) { return false, "" }
func (dbp *Process) Restart(string) error { return proc.NotRecordedErr }
func (dbp *Process) Direction(proc.Direction) error { return proc.NotRecordedErr }
func (dbp *Process) When() (string, error) { return "", nil }
func (dbp *Process) Checkpoint(string) (int, error) { return -1, proc.NotRecordedErr }
func (dbp *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.NotRecordedErr }
func (dbp *Process) ClearCheckpoint(int) error { return proc.NotRecordedErr }
// Detach from the process being debugged, optionally killing it.
func (dbp *Process) Detach(kill bool) (err error) {
if dbp.exited {
return nil
}
if kill && dbp.childProcess {
err := dbp.Kill()
if err != nil {
return err
}
dbp.bi.Close()
return nil
}
if !kill {
// Clean up any breakpoints we've set.
for _, bp := range dbp.breakpoints.M {
if bp != nil {
_, err := dbp.ClearBreakpoint(bp.Addr)
if err != nil {
return err
}
}
}
}
dbp.execPtraceFunc(func() {
err = dbp.detach(kill)
if err != nil {
return
}
if kill {
err = killProcess(dbp.pid)
}
})
dbp.bi.Close()
return
}
// Exited returns whether the debugged
// process has exited.
func (dbp *Process) Exited() bool {
return dbp.exited
}
func (dbp *Process) ResumeNotify(ch chan<- struct{}) {
dbp.resumeChan = ch
}
func (dbp *Process) Pid() int {
return dbp.pid
}
func (dbp *Process) SelectedGoroutine() *proc.G {
return dbp.selectedGoroutine
}
func (dbp *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(dbp.threads))
for _, v := range dbp.threads {
r = append(r, v)
}
return r
}
func (dbp *Process) FindThread(threadID int) (proc.Thread, bool) {
th, ok := dbp.threads[threadID]
return th, ok
}
func (dbp *Process) CurrentThread() proc.Thread {
return dbp.currentThread
}
func (dbp *Process) Breakpoints() *proc.BreakpointMap {
return &dbp.breakpoints
}
// LoadInformation finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *Process) LoadInformation(path string) error {
var wg sync.WaitGroup
path = findExecutable(path, dbp.pid)
wg.Add(1)
go dbp.loadProcessInformation(&wg)
err := dbp.bi.LoadBinaryInfo(path, &wg)
wg.Wait()
if err == nil {
err = dbp.bi.LoadError()
}
return err
}
// RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error {
if dbp.exited {
return &proc.ProcessExitedError{Pid: dbp.Pid()}
}
dbp.haltMu.Lock()
defer dbp.haltMu.Unlock()
dbp.manualStopRequested = true
dbp.halt = true
return dbp.requestManualStop()
}
func (dbp *Process) ManualStopRequested() bool {
dbp.haltMu.Lock()
msr := dbp.manualStopRequested
dbp.manualStopRequested = false
dbp.haltMu.Unlock()
return msr
}
func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
f, l, fn := dbp.bi.PCToLine(uint64(addr))
if fn == nil {
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
}
originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
if err != nil {
return "", 0, nil, nil, err
}
if err := dbp.writeSoftwareBreakpoint(dbp.currentThread, addr); err != nil {
return "", 0, nil, nil, err
}
return f, l, fn, originalData, nil
}
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
// break point table.
func (dbp *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
return dbp.breakpoints.Set(addr, kind, cond, dbp.writeBreakpoint)
}
// ClearBreakpoint clears the breakpoint at addr.
func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
if dbp.exited {
return nil, &proc.ProcessExitedError{Pid: dbp.Pid()}
}
return dbp.breakpoints.Clear(addr, dbp.currentThread.ClearBreakpoint)
}
func (dbp *Process) ContinueOnce() (proc.Thread, error) {
if dbp.exited {
return nil, &proc.ProcessExitedError{Pid: dbp.Pid()}
}
if err := dbp.resume(); err != nil {
return nil, err
}
dbp.allGCache = nil
for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear()
}
if dbp.resumeChan != nil {
close(dbp.resumeChan)
dbp.resumeChan = nil
}
trapthread, err := dbp.trapWait(-1)
if err != nil {
return nil, err
}
if err := dbp.Halt(); err != nil {
return nil, dbp.exitGuard(err)
}
if err := dbp.setCurrentBreakpoints(trapthread); err != nil {
return nil, err
}
return trapthread, err
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// asssociated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Process) StepInstruction() (err error) {
thread := dbp.currentThread
if dbp.selectedGoroutine != nil {
if dbp.selectedGoroutine.Thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(dbp.selectedGoroutine.PC, proc.NextBreakpoint, proc.SameGoroutineCondition(dbp.selectedGoroutine)); err != nil {
return err
}
return proc.Continue(dbp)
}
thread = dbp.selectedGoroutine.Thread.(*Thread)
}
dbp.allGCache = nil
if dbp.exited {
return &proc.ProcessExitedError{Pid: dbp.Pid()}
}
thread.CurrentBreakpoint.Clear()
err = thread.StepInstruction()
if err != nil {
return err
}
err = thread.SetCurrentBreakpoint()
if err != nil {
return err
}
if g, _ := proc.GetG(thread); g != nil {
dbp.selectedGoroutine = g
}
return nil
}
// SwitchThread changes from current thread to the thread specified by `tid`.
func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited {
return &proc.ProcessExitedError{Pid: dbp.Pid()}
}
if th, ok := dbp.threads[tid]; ok {
dbp.currentThread = th
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// SwitchGoroutine changes from current thread to the thread
// running the specified goroutine.
func (dbp *Process) SwitchGoroutine(gid int) error {
if dbp.exited {
return &proc.ProcessExitedError{Pid: dbp.Pid()}
}
g, err := proc.FindGoroutine(dbp, gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil
}
if g.Thread != nil {
return dbp.SwitchThread(g.Thread.ThreadID())
}
dbp.selectedGoroutine = g
return nil
}
// Halt stops all threads.
func (dbp *Process) Halt() (err error) {
if dbp.exited {
return &proc.ProcessExitedError{Pid: dbp.Pid()}
}
for _, th := range dbp.threads {
if err := th.Halt(); err != nil {
return err
}
}
return nil
}
// FindBreakpoint finds the breakpoint for the given pc.
func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
if bp, ok := dbp.breakpoints.M[pc-uint64(dbp.bi.Arch.BreakpointSize())]; ok {
return bp, true
}
// Directly use addr to lookup breakpoint.
if bp, ok := dbp.breakpoints.M[pc]; ok {
return bp, true
}
return nil, false
}
// Returns a new Process struct.
func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
err := dbp.LoadInformation(path)
if err != nil {
return dbp, err
}
if err := dbp.updateThreadList(); err != nil {
return dbp, err
}
// selectedGoroutine can not be set correctly by the call to updateThreadList
// because without calling SetGStructOffset we can not read the G struct of currentThread
// but without calling updateThreadList we can not examine memory to determine
// the offset of g struct inside TLS
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
panicpc, err := proc.FindFunctionLocation(dbp, "runtime.startpanic", true, 0)
if err == nil {
bp, err := dbp.breakpoints.SetWithID(-1, panicpc, dbp.writeBreakpoint)
if err == nil {
bp.Name = proc.UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
}
}
return dbp, nil
}
func (dbp *Process) ClearInternalBreakpoints() error {
return dbp.breakpoints.ClearInternalBreakpoints(func(bp *proc.Breakpoint) error {
if err := dbp.currentThread.ClearBreakpoint(bp); err != nil {
return err
}
for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint == bp {
thread.CurrentBreakpoint.Clear()
}
}
return nil
})
}
func (dbp *Process) handlePtraceFuncs() {
// We must ensure here that we are running on the same thread during
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
for fn := range dbp.ptraceChan {
fn()
dbp.ptraceDoneChan <- nil
}
}
func (dbp *Process) execPtraceFunc(fn func()) {
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}
func (dbp *Process) postExit() {
dbp.exited = true
close(dbp.ptraceChan)
close(dbp.ptraceDoneChan)
dbp.bi.Close()
}
func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
_, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction())
return err
}
func (dbp *Process) AllGCache() *[]*proc.G {
return &dbp.allGCache
}