
Process is an incorrect name for the DebuggedProcess struct that the thread is "a part" of. Also, no need to export that field.
290 lines
7.2 KiB
Go
290 lines
7.2 KiB
Go
package proctl
|
|
|
|
import (
|
|
"debug/gosym"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/derekparker/delve/dwarf/frame"
|
|
"github.com/derekparker/delve/source"
|
|
sys "golang.org/x/sys/unix"
|
|
)
|
|
|
|
// ThreadContext represents a single thread in the traced process
|
|
// Id represents the thread id, Process holds a reference to the
|
|
// DebuggedProcess struct that contains info on the process as
|
|
// a whole, and Status represents the last result of a `wait` call
|
|
// on this thread.
|
|
type ThreadContext struct {
|
|
Id int
|
|
Status *sys.WaitStatus
|
|
CurrentBreakpoint *BreakPoint
|
|
dbp *DebuggedProcess
|
|
singleStepping bool
|
|
running bool
|
|
os *OSSpecificDetails
|
|
}
|
|
|
|
type Location struct {
|
|
PC uint64
|
|
File string
|
|
Line int
|
|
Fn *gosym.Func
|
|
}
|
|
|
|
// Continue the execution of this thread. This method takes
|
|
// software breakpoints into consideration and ensures that
|
|
// we step over any breakpoints. It will restore the instruction,
|
|
// step, and then restore the breakpoint and continue.
|
|
func (thread *ThreadContext) Continue() error {
|
|
pc, err := thread.PC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check whether we are stopped at a breakpoint, and
|
|
// if so, single step over it before continuing.
|
|
if _, ok := thread.dbp.BreakPoints[pc]; ok {
|
|
if err := thread.Step(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return thread.resume()
|
|
}
|
|
|
|
// Single steps this thread a single instruction, ensuring that
|
|
// we correctly handle the likely case that we are at a breakpoint.
|
|
func (thread *ThreadContext) Step() (err error) {
|
|
thread.singleStepping = true
|
|
defer func() { thread.singleStepping = false }()
|
|
pc, err := thread.PC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bp, ok := thread.dbp.BreakPoints[pc]
|
|
if ok {
|
|
// Clear the breakpoint so that we can continue execution.
|
|
_, err = thread.dbp.Clear(bp.Addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Restore breakpoint now that we have passed it.
|
|
defer func() {
|
|
var nbp *BreakPoint
|
|
nbp, err = thread.dbp.Break(bp.Addr)
|
|
nbp.Temp = bp.Temp
|
|
}()
|
|
}
|
|
|
|
err = thread.singleStep()
|
|
if err != nil {
|
|
return fmt.Errorf("step failed: %s", err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Call a function named `name`. This is currently _NOT_ safe.
|
|
func (thread *ThreadContext) CallFn(name string, fn func() error) error {
|
|
f := thread.dbp.goSymTable.LookupFunc(name)
|
|
if f == nil {
|
|
return fmt.Errorf("could not find function %s", name)
|
|
}
|
|
|
|
// Set breakpoint at the end of the function (before it returns).
|
|
bp, err := thread.TempBreak(f.End - 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer thread.dbp.Clear(bp.Addr)
|
|
|
|
regs, err := thread.saveRegisters()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
previousFrame := make([]byte, f.FrameSize)
|
|
frameSize := uintptr(regs.SP() + uint64(f.FrameSize))
|
|
if _, err := readMemory(thread, frameSize, previousFrame); err != nil {
|
|
return err
|
|
}
|
|
defer func() { writeMemory(thread, frameSize, previousFrame) }()
|
|
|
|
if err = thread.SetPC(f.Entry); err != nil {
|
|
return err
|
|
}
|
|
defer thread.restoreRegisters()
|
|
if err = thread.Continue(); err != nil {
|
|
return err
|
|
}
|
|
th, err := thread.dbp.trapWait(-1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
th.CurrentBreakpoint = nil
|
|
return fn()
|
|
}
|
|
|
|
// Set breakpoint using this thread.
|
|
func (thread *ThreadContext) Break(addr uint64) (*BreakPoint, error) {
|
|
return thread.dbp.setBreakpoint(thread.Id, addr, false)
|
|
}
|
|
|
|
// Set breakpoint using this thread.
|
|
func (thread *ThreadContext) TempBreak(addr uint64) (*BreakPoint, error) {
|
|
return thread.dbp.setBreakpoint(thread.Id, addr, true)
|
|
}
|
|
|
|
// Clear breakpoint using this thread.
|
|
func (thread *ThreadContext) Clear(addr uint64) (*BreakPoint, error) {
|
|
return thread.dbp.clearBreakpoint(thread.Id, addr)
|
|
}
|
|
|
|
func (thread *ThreadContext) Location() (*Location, error) {
|
|
pc, err := thread.PC()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f, l, fn := thread.dbp.PCToLine(pc)
|
|
return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
|
|
}
|
|
|
|
// Step to next source line.
|
|
//
|
|
// Next will step over functions, and will follow through to the
|
|
// return address of a function.
|
|
//
|
|
// This functionality is implemented by finding all possible next lines
|
|
// and setting a breakpoint at them. Once we've set a breakpoint at each
|
|
// potential line, we continue the thread.
|
|
func (thread *ThreadContext) Next() (err error) {
|
|
curpc, err := thread.PC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Grab info on our current stack frame. Used to determine
|
|
// whether we may be stepping outside of the current function.
|
|
fde, err := thread.dbp.frameEntries.FDEForPC(curpc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get current file/line.
|
|
loc, err := thread.Location()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if filepath.Ext(loc.File) == ".go" {
|
|
if err = thread.next(curpc, fde, loc.File, loc.Line); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err = thread.cnext(curpc, fde); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return thread.Continue()
|
|
}
|
|
|
|
// Go routine is exiting.
|
|
type GoroutineExitingError struct {
|
|
goid int
|
|
}
|
|
|
|
func (ge GoroutineExitingError) Error() string {
|
|
return fmt.Sprintf("goroutine %d is exiting", ge.goid)
|
|
}
|
|
|
|
// This version of next uses the AST from the current source file to figure out all of the potential source lines
|
|
// we could end up at.
|
|
func (thread *ThreadContext) next(curpc uint64, fde *frame.FrameDescriptionEntry, file string, line int) error {
|
|
lines, err := thread.dbp.ast.NextLines(file, line)
|
|
if err != nil {
|
|
if _, ok := err.(source.NoNodeError); !ok {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ret, err := thread.ReturnAddress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pcs := make([]uint64, 0, len(lines))
|
|
for i := range lines {
|
|
pcs = append(pcs, thread.dbp.lineInfo.AllPCsForFileLine(file, lines[i])...)
|
|
}
|
|
|
|
var covered bool
|
|
for i := range pcs {
|
|
if fde.Cover(pcs[i]) {
|
|
covered = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !covered {
|
|
fn := thread.dbp.goSymTable.PCToFunc(ret)
|
|
if fn != nil && fn.Name == "runtime.goexit" {
|
|
g, err := thread.curG()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return GoroutineExitingError{goid: g.Id}
|
|
}
|
|
}
|
|
pcs = append(pcs, ret)
|
|
return thread.setNextTempBreakpoints(curpc, pcs)
|
|
}
|
|
|
|
// Set a breakpoint at every reachable location, as well as the return address. Without
|
|
// the benefit of an AST we can't be sure we're not at a branching statement and thus
|
|
// cannot accurately predict where we may end up.
|
|
func (thread *ThreadContext) cnext(curpc uint64, fde *frame.FrameDescriptionEntry) error {
|
|
pcs := thread.dbp.lineInfo.AllPCsBetween(fde.Begin(), fde.End())
|
|
ret, err := thread.ReturnAddress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pcs = append(pcs, ret)
|
|
return thread.setNextTempBreakpoints(curpc, pcs)
|
|
}
|
|
|
|
func (thread *ThreadContext) setNextTempBreakpoints(curpc uint64, pcs []uint64) error {
|
|
for i := range pcs {
|
|
if pcs[i] == curpc || pcs[i] == curpc-1 {
|
|
continue
|
|
}
|
|
if _, err := thread.dbp.TempBreak(pcs[i]); err != nil {
|
|
if err, ok := err.(BreakPointExistsError); !ok {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Sets the PC for this thread.
|
|
func (thread *ThreadContext) SetPC(pc uint64) error {
|
|
regs, err := thread.Registers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return regs.SetPC(thread, pc)
|
|
}
|
|
|
|
func (thread *ThreadContext) curG() (*G, error) {
|
|
var g *G
|
|
err := thread.CallFn("runtime.getg", func() error {
|
|
regs, err := thread.Registers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g, err = parseG(thread, regs.SP()+uint64(thread.dbp.arch.PtrSize()))
|
|
return err
|
|
})
|
|
return g, err
|
|
}
|