proc,proc/*: add StopReason field to Target (#1877)
* proc,proc/*: move SelectedGoroutine to proc.Target, remove PostInitializationSetup moves SelectedGoroutine, SwitchThread and SwitchGoroutine to proc.Target, merges PostInitializationSetup with NewTarget. * proc,proc/*: add StopReason field to Target Adds a StopReason field to the Target object describing why the target process is currently stopped. This will be useful for the DAP server (which needs to report this reason in one of its requests) as well as making pull request #1785 (reverse step) conformant to the new architecture. * proc: collect NewTarget arguments into a struct
This commit is contained in:
parent
f3a191cd73
commit
9f97edb0bb
@ -155,12 +155,13 @@ type Process struct {
|
||||
|
||||
entryPoint uint64
|
||||
|
||||
bi *proc.BinaryInfo
|
||||
breakpoints proc.BreakpointMap
|
||||
currentThread *Thread
|
||||
selectedGoroutine *proc.G
|
||||
bi *proc.BinaryInfo
|
||||
breakpoints proc.BreakpointMap
|
||||
currentThread *Thread
|
||||
}
|
||||
|
||||
var _ proc.ProcessInternal = &Process{}
|
||||
|
||||
// Thread represents a thread in the core file being debugged.
|
||||
type Thread struct {
|
||||
th osThread
|
||||
@ -212,17 +213,12 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := p.initialize(exePath, debugInfoDirs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proc.NewTarget(p, false), nil
|
||||
}
|
||||
|
||||
// initialize for core files doesn't do much
|
||||
// aside from call the post initialization setup.
|
||||
func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
|
||||
return proc.NewTarget(p, proc.NewTargetConfig{
|
||||
Path: exePath,
|
||||
DebugInfoDirs: debugInfoDirs,
|
||||
WriteBreakpoint: p.writeBreakpoint,
|
||||
DisableAsyncPreempt: false,
|
||||
StopReason: proc.StopAttached})
|
||||
}
|
||||
|
||||
// BinInfo will return the binary info.
|
||||
@ -230,13 +226,6 @@ func (p *Process) BinInfo() *proc.BinaryInfo {
|
||||
return p.bi
|
||||
}
|
||||
|
||||
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||
// the default for any command executed, the goroutine being actively
|
||||
// followed.
|
||||
func (p *Process) SetSelectedGoroutine(g *proc.G) {
|
||||
p.selectedGoroutine = g
|
||||
}
|
||||
|
||||
// EntryPoint will return the entry point address for this core file.
|
||||
func (p *Process) EntryPoint() (uint64, error) {
|
||||
return p.entryPoint, nil
|
||||
@ -392,8 +381,8 @@ func (p *Process) ClearInternalBreakpoints() error {
|
||||
|
||||
// ContinueOnce will always return an error because you
|
||||
// cannot control execution of a core file.
|
||||
func (p *Process) ContinueOnce() (proc.Thread, error) {
|
||||
return nil, ErrContinueCore
|
||||
func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
|
||||
return nil, proc.StopUnknown, ErrContinueCore
|
||||
}
|
||||
|
||||
// StepInstruction will always return an error
|
||||
@ -443,40 +432,11 @@ func (p *Process) Pid() int {
|
||||
func (p *Process) ResumeNotify(chan<- struct{}) {
|
||||
}
|
||||
|
||||
// SelectedGoroutine returns the current active and selected
|
||||
// goroutine.
|
||||
func (p *Process) SelectedGoroutine() *proc.G {
|
||||
return p.selectedGoroutine
|
||||
}
|
||||
|
||||
// SetBreakpoint will always return an error for core files as you cannot write memory or control execution.
|
||||
func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
|
||||
return nil, ErrWriteCore
|
||||
}
|
||||
|
||||
// SwitchGoroutine will change the selected and active goroutine.
|
||||
func (p *Process) SwitchGoroutine(g *proc.G) error {
|
||||
if g == nil {
|
||||
// user specified -1 and selectedGoroutine is nil
|
||||
return nil
|
||||
}
|
||||
if g.Thread != nil {
|
||||
return p.SwitchThread(g.Thread.ThreadID())
|
||||
}
|
||||
p.selectedGoroutine = g
|
||||
return nil
|
||||
}
|
||||
|
||||
// SwitchThread will change the selected and active thread.
|
||||
func (p *Process) SwitchThread(tid int) error {
|
||||
if th, ok := p.Threads[tid]; ok {
|
||||
p.currentThread = th
|
||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("thread %d does not exist", tid)
|
||||
}
|
||||
|
||||
// ThreadList will return a list of all threads currently in the process.
|
||||
func (p *Process) ThreadList() []proc.Thread {
|
||||
r := make([]proc.Thread, 0, len(p.Threads))
|
||||
@ -491,3 +451,8 @@ func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
|
||||
t, ok := p.Threads[threadID]
|
||||
return t, ok
|
||||
}
|
||||
|
||||
// SetCurrentThread is used internally by proc.Target to change the current thread.
|
||||
func (p *Process) SetCurrentThread(th proc.Thread) {
|
||||
p.currentThread = th.(*Thread)
|
||||
}
|
||||
|
||||
@ -104,9 +104,8 @@ type Process struct {
|
||||
bi *proc.BinaryInfo
|
||||
conn gdbConn
|
||||
|
||||
threads map[int]*Thread
|
||||
currentThread *Thread
|
||||
selectedGoroutine *proc.G
|
||||
threads map[int]*Thread
|
||||
currentThread *Thread
|
||||
|
||||
exited, detached bool
|
||||
ctrlC bool // ctrl-c was sent to stop inferior
|
||||
@ -125,9 +124,10 @@ type Process struct {
|
||||
waitChan chan *os.ProcessState
|
||||
|
||||
onDetach func() // called after a successful detach
|
||||
|
||||
}
|
||||
|
||||
var _ proc.ProcessInternal = &Process{}
|
||||
|
||||
// Thread represents an operating system thread.
|
||||
type Thread struct {
|
||||
ID int
|
||||
@ -198,7 +198,7 @@ func New(process *os.Process) *Process {
|
||||
}
|
||||
|
||||
// Listen waits for a connection from the stub.
|
||||
func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string) error {
|
||||
func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
||||
acceptChan := make(chan net.Conn)
|
||||
|
||||
go func() {
|
||||
@ -210,25 +210,25 @@ func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoD
|
||||
case conn := <-acceptChan:
|
||||
listener.Close()
|
||||
if conn == nil {
|
||||
return errors.New("could not connect")
|
||||
return nil, errors.New("could not connect")
|
||||
}
|
||||
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||
return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
|
||||
case status := <-p.waitChan:
|
||||
listener.Close()
|
||||
return fmt.Errorf("stub exited while waiting for connection: %v", status)
|
||||
return nil, fmt.Errorf("stub exited while waiting for connection: %v", status)
|
||||
}
|
||||
}
|
||||
|
||||
// Dial attempts to connect to the stub.
|
||||
func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string) error {
|
||||
func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
||||
for {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||
return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
|
||||
}
|
||||
select {
|
||||
case status := <-p.waitChan:
|
||||
return fmt.Errorf("stub exited while attempting to connect: %v", status)
|
||||
return nil, fmt.Errorf("stub exited while attempting to connect: %v", status)
|
||||
default:
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
@ -241,13 +241,13 @@ func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string
|
||||
// program and the PID of the target process, both are optional, however
|
||||
// some stubs do not provide ways to determine path and pid automatically
|
||||
// and Connect will be unable to function without knowing them.
|
||||
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
|
||||
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
||||
p.conn.conn = conn
|
||||
p.conn.pid = pid
|
||||
err := p.conn.handshake()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if verbuf, err := p.conn.exec([]byte("$qGDBServerVersion"), "init"); err == nil {
|
||||
@ -262,8 +262,9 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.initialize(path, debugInfoDirs); err != nil {
|
||||
return err
|
||||
tgt, err := p.initialize(path, debugInfoDirs, stopReason)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// None of the stubs we support returns the value of fs_base or gs_base
|
||||
@ -279,7 +280,8 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
|
||||
p.loadGInstrAddr = addr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
// unusedPort returns an unused tcp port
|
||||
@ -402,15 +404,13 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
|
||||
p := New(process.Process)
|
||||
p.conn.isDebugserver = isDebugserver
|
||||
|
||||
var tgt *proc.Target
|
||||
if listener != nil {
|
||||
err = p.Listen(listener, cmd[0], 0, debugInfoDirs)
|
||||
tgt, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
||||
} else {
|
||||
err = p.Dial(port, cmd[0], 0, debugInfoDirs)
|
||||
tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(p, runtime.GOOS == "darwin"), nil
|
||||
return tgt, err
|
||||
}
|
||||
|
||||
// LLDBAttach starts an instance of lldb-server and connects to it, asking
|
||||
@ -454,15 +454,13 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
|
||||
p := New(process.Process)
|
||||
p.conn.isDebugserver = isDebugserver
|
||||
|
||||
var tgt *proc.Target
|
||||
if listener != nil {
|
||||
err = p.Listen(listener, path, pid, debugInfoDirs)
|
||||
tgt, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached)
|
||||
} else {
|
||||
err = p.Dial(port, path, pid, debugInfoDirs)
|
||||
tgt, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(p, false), nil
|
||||
return tgt, err
|
||||
}
|
||||
|
||||
// EntryPoint will return the process entry point address, useful for
|
||||
@ -481,7 +479,7 @@ func (p *Process) EntryPoint() (uint64, error) {
|
||||
// initialize uses qProcessInfo to load the inferior's PID and
|
||||
// executable path. This command is not supported by all stubs and not all
|
||||
// stubs will report both the PID and executable path.
|
||||
func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
func (p *Process) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
||||
var err error
|
||||
if path == "" {
|
||||
// If we are attaching to a running process and the user didn't specify
|
||||
@ -495,11 +493,11 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
_, path, err = queryProcessInfo(p, p.Pid())
|
||||
if err != nil {
|
||||
p.conn.conn.Close()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
p.conn.conn.Close()
|
||||
return fmt.Errorf("could not determine executable path: %v", err)
|
||||
return nil, fmt.Errorf("could not determine executable path: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -520,7 +518,7 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
if err != nil {
|
||||
p.conn.conn.Close()
|
||||
p.bi.Close()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
p.clearThreadSignals()
|
||||
|
||||
@ -529,14 +527,20 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
if err != nil && !isProtocolErrorUnsupported(err) {
|
||||
p.conn.conn.Close()
|
||||
p.bi.Close()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil {
|
||||
tgt, err := proc.NewTarget(p, proc.NewTargetConfig{
|
||||
Path: path,
|
||||
DebugInfoDirs: debugInfoDirs,
|
||||
WriteBreakpoint: p.writeBreakpoint,
|
||||
DisableAsyncPreempt: runtime.GOOS == "darwin",
|
||||
StopReason: stopReason})
|
||||
if err != nil {
|
||||
p.conn.conn.Close()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func queryProcessInfo(p *Process, pid int) (int, string, error) {
|
||||
@ -606,9 +610,9 @@ func (p *Process) CurrentThread() proc.Thread {
|
||||
return p.currentThread
|
||||
}
|
||||
|
||||
// SelectedGoroutine returns the current actuve selected goroutine.
|
||||
func (p *Process) SelectedGoroutine() *proc.G {
|
||||
return p.selectedGoroutine
|
||||
// SetCurrentThread is used internally by proc.Target to change the current thread.
|
||||
func (p *Process) SetCurrentThread(th proc.Thread) {
|
||||
p.currentThread = th.(*Thread)
|
||||
}
|
||||
|
||||
const (
|
||||
@ -628,9 +632,9 @@ const (
|
||||
|
||||
// ContinueOnce will continue execution of the process until
|
||||
// a breakpoint is hit or signal is received.
|
||||
func (p *Process) ContinueOnce() (proc.Thread, error) {
|
||||
func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
|
||||
if p.exited {
|
||||
return nil, &proc.ErrProcessExited{Pid: p.conn.pid}
|
||||
return nil, proc.StopExited, &proc.ErrProcessExited{Pid: p.conn.pid}
|
||||
}
|
||||
|
||||
if p.conn.direction == proc.Forward {
|
||||
@ -638,7 +642,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
|
||||
for _, thread := range p.threads {
|
||||
if thread.CurrentBreakpoint.Breakpoint != nil {
|
||||
if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil {
|
||||
return nil, err
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -664,7 +668,7 @@ continueLoop:
|
||||
if _, exited := err.(proc.ErrProcessExited); exited {
|
||||
p.exited = true
|
||||
}
|
||||
return nil, err
|
||||
return nil, proc.StopExited, err
|
||||
}
|
||||
|
||||
// For stubs that support qThreadStopInfo updateThreadList will
|
||||
@ -689,16 +693,16 @@ continueLoop:
|
||||
|
||||
if p.BinInfo().GOOS == "linux" {
|
||||
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
|
||||
return nil, err
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.setCurrentBreakpoints(); err != nil {
|
||||
return nil, err
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
|
||||
if trapthread == nil {
|
||||
return nil, fmt.Errorf("could not find thread %s", threadID)
|
||||
return nil, proc.StopUnknown, fmt.Errorf("could not find thread %s", threadID)
|
||||
}
|
||||
|
||||
var err error
|
||||
@ -720,7 +724,7 @@ continueLoop:
|
||||
// the signals that are reported here can not be propagated back to the target process.
|
||||
trapthread.sig = 0
|
||||
}
|
||||
return trapthread, err
|
||||
return trapthread, proc.StopUnknown, err
|
||||
}
|
||||
|
||||
func (p *Process) findThreadByStrID(threadID string) *Thread {
|
||||
@ -808,38 +812,6 @@ func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread
|
||||
return trapthread, shouldStop
|
||||
}
|
||||
|
||||
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||
// the default for any command executed, the goroutine being actively
|
||||
// followed.
|
||||
func (p *Process) SetSelectedGoroutine(g *proc.G) {
|
||||
p.selectedGoroutine = g
|
||||
}
|
||||
|
||||
// SwitchThread will change the internal selected thread.
|
||||
func (p *Process) SwitchThread(tid int) error {
|
||||
if p.exited {
|
||||
return proc.ErrProcessExited{Pid: p.conn.pid}
|
||||
}
|
||||
if th, ok := p.threads[tid]; ok {
|
||||
p.currentThread = th
|
||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("thread %d does not exist", tid)
|
||||
}
|
||||
|
||||
// SwitchGoroutine will change the internal selected goroutine.
|
||||
func (p *Process) SwitchGoroutine(g *proc.G) error {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
if g.Thread != nil {
|
||||
return p.SwitchThread(g.Thread.ThreadID())
|
||||
}
|
||||
p.selectedGoroutine = g
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestManualStop will attempt to stop the process
|
||||
// without a breakpoint or signal having been recieved.
|
||||
func (p *Process) RequestManualStop() error {
|
||||
@ -944,7 +916,6 @@ func (p *Process) Restart(pos string) error {
|
||||
}
|
||||
p.clearThreadSignals()
|
||||
p.clearThreadRegisters()
|
||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||
|
||||
for addr := range p.breakpoints.M {
|
||||
p.conn.setBreakpoint(addr)
|
||||
|
||||
@ -90,13 +90,13 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
|
||||
safeRemoveAll(p.tracedir)
|
||||
}
|
||||
}
|
||||
err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
|
||||
tgt, err := p.Dial(init.port, init.exe, 0, debugInfoDirs, proc.StopLaunched)
|
||||
if err != nil {
|
||||
rrcmd.Process.Kill()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proc.NewTarget(p, false), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
// ErrPerfEventParanoid is the error returned by Reply and Record if
|
||||
|
||||
@ -18,16 +18,27 @@ type Process interface {
|
||||
RecordingManipulation
|
||||
}
|
||||
|
||||
// RecordingManipulation is an interface for manipulating process recordings.
|
||||
type RecordingManipulation interface {
|
||||
// Recorded returns true if the current process is a recording and the path
|
||||
// to the trace directory.
|
||||
Recorded() (recorded bool, tracedir string)
|
||||
// ProcessInternal holds a set of methods that are not meant to be called by
|
||||
// anyone except for an instance of `proc.Target`. These methods are not
|
||||
// safe to use by themselves and should never be called directly outside of
|
||||
// the `proc` package.
|
||||
// This is temporary and in support of an ongoing refactor.
|
||||
type ProcessInternal interface {
|
||||
SetCurrentThread(Thread)
|
||||
// Restart restarts the recording from the specified position, or from the
|
||||
// last checkpoint if pos == "".
|
||||
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
|
||||
// number.
|
||||
Restart(pos string) error
|
||||
Detach(bool) error
|
||||
ContinueOnce() (trapthread Thread, stopReason StopReason, err error)
|
||||
}
|
||||
|
||||
// RecordingManipulation is an interface for manipulating process recordings.
|
||||
type RecordingManipulation interface {
|
||||
// Recorded returns true if the current process is a recording and the path
|
||||
// to the trace directory.
|
||||
Recorded() (recorded bool, tracedir string)
|
||||
// Direction changes execution direction.
|
||||
Direction(Direction) error
|
||||
// When returns current recording position.
|
||||
@ -71,7 +82,6 @@ type Info interface {
|
||||
EntryPoint() (uint64, error)
|
||||
|
||||
ThreadInfo
|
||||
GoroutineInfo
|
||||
}
|
||||
|
||||
// ThreadInfo is an interface for getting information on active threads
|
||||
@ -82,22 +92,12 @@ type ThreadInfo interface {
|
||||
CurrentThread() Thread
|
||||
}
|
||||
|
||||
// GoroutineInfo is an interface for getting information on running goroutines.
|
||||
type GoroutineInfo interface {
|
||||
SelectedGoroutine() *G
|
||||
SetSelectedGoroutine(*G)
|
||||
}
|
||||
|
||||
// ProcessManipulation is an interface for changing the execution state of a process.
|
||||
type ProcessManipulation interface {
|
||||
ContinueOnce() (trapthread Thread, err error)
|
||||
SwitchThread(int) error
|
||||
SwitchGoroutine(*G) error
|
||||
RequestManualStop() error
|
||||
// CheckAndClearManualStopRequest returns true the first time it's called
|
||||
// after a call to RequestManualStop.
|
||||
CheckAndClearManualStopRequest() bool
|
||||
Detach(bool) error
|
||||
}
|
||||
|
||||
// BreakpointManipulation is an interface for managing breakpoints.
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"runtime"
|
||||
"sync"
|
||||
@ -26,10 +25,6 @@ type Process struct {
|
||||
// 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
|
||||
|
||||
os *OSProcessDetails
|
||||
firstStart bool
|
||||
stopMu sync.Mutex
|
||||
@ -42,6 +37,8 @@ type Process struct {
|
||||
exited, detached bool
|
||||
}
|
||||
|
||||
var _ proc.ProcessInternal = &Process{}
|
||||
|
||||
// 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
|
||||
@ -151,12 +148,6 @@ func (dbp *Process) Pid() int {
|
||||
return dbp.pid
|
||||
}
|
||||
|
||||
// SelectedGoroutine returns the current selected,
|
||||
// active goroutine.
|
||||
func (dbp *Process) SelectedGoroutine() *proc.G {
|
||||
return dbp.selectedGoroutine
|
||||
}
|
||||
|
||||
// ThreadList returns a list of threads in the process.
|
||||
func (dbp *Process) ThreadList() []proc.Thread {
|
||||
r := make([]proc.Thread, 0, len(dbp.threads))
|
||||
@ -177,6 +168,11 @@ func (dbp *Process) CurrentThread() proc.Thread {
|
||||
return dbp.currentThread
|
||||
}
|
||||
|
||||
// SetCurrentThread is used internally by proc.Target to change the current thread.
|
||||
func (p *Process) SetCurrentThread(th proc.Thread) {
|
||||
p.currentThread = th.(*Thread)
|
||||
}
|
||||
|
||||
// Breakpoints returns a list of breakpoints currently set.
|
||||
func (dbp *Process) Breakpoints() *proc.BreakpointMap {
|
||||
return &dbp.breakpoints
|
||||
@ -237,13 +233,13 @@ func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
|
||||
|
||||
// ContinueOnce will continue the target until it stops.
|
||||
// This could be the result of a breakpoint or signal.
|
||||
func (dbp *Process) ContinueOnce() (proc.Thread, error) {
|
||||
func (dbp *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
|
||||
if dbp.exited {
|
||||
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
|
||||
if err := dbp.resume(); err != nil {
|
||||
return nil, err
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
|
||||
for _, th := range dbp.threads {
|
||||
@ -257,42 +253,12 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
|
||||
|
||||
trapthread, err := dbp.trapWait(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
if err := dbp.stop(trapthread); err != nil {
|
||||
return nil, err
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
return trapthread, err
|
||||
}
|
||||
|
||||
// SwitchThread changes from current thread to the thread specified by `tid`.
|
||||
func (dbp *Process) SwitchThread(tid int) error {
|
||||
if dbp.exited {
|
||||
return &proc.ErrProcessExited{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(g *proc.G) error {
|
||||
if dbp.exited {
|
||||
return &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
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
|
||||
return trapthread, proc.StopUnknown, err
|
||||
}
|
||||
|
||||
// FindBreakpoint finds the breakpoint for the given pc.
|
||||
@ -312,21 +278,23 @@ func (dbp *Process) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakpoint,
|
||||
|
||||
// initialize will ensure that all relevant information is loaded
|
||||
// so the process is ready to be debugged.
|
||||
func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
func (dbp *Process) initialize(path string, debugInfoDirs []string) (*proc.Target, error) {
|
||||
if err := initialize(dbp); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := dbp.updateThreadList(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint)
|
||||
}
|
||||
|
||||
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||
// the default for any command executed, the goroutine being actively
|
||||
// followed.
|
||||
func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
|
||||
dbp.selectedGoroutine = g
|
||||
stopReason := proc.StopLaunched
|
||||
if !dbp.childProcess {
|
||||
stopReason = proc.StopAttached
|
||||
}
|
||||
return proc.NewTarget(dbp, proc.NewTargetConfig{
|
||||
Path: path,
|
||||
DebugInfoDirs: debugInfoDirs,
|
||||
WriteBreakpoint: dbp.writeBreakpoint,
|
||||
DisableAsyncPreempt: runtime.GOOS == "windows",
|
||||
StopReason: stopReason})
|
||||
}
|
||||
|
||||
// ClearInternalBreakpoints will clear all non-user set breakpoints. These
|
||||
|
||||
@ -117,16 +117,14 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
|
||||
}
|
||||
|
||||
dbp.os.initialized = true
|
||||
err = dbp.initialize(argv0Go, []string{})
|
||||
dbp.currentThread = trapthread
|
||||
|
||||
tgt, err := dbp.initialize(argv0Go, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dbp.SwitchThread(trapthread.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proc.NewTarget(dbp, false), err
|
||||
return tgt, err
|
||||
}
|
||||
|
||||
// Attach to an existing process with the given PID.
|
||||
@ -153,12 +151,12 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dbp.initialize("", []string{})
|
||||
tgt, err := dbp.initialize("", []string{})
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, false), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
// Kill kills the process.
|
||||
@ -269,7 +267,7 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
|
||||
dbp.threads[port] = thread
|
||||
thread.os.threadAct = C.thread_act_t(port)
|
||||
if dbp.currentThread == nil {
|
||||
dbp.SwitchThread(thread.ID)
|
||||
dbp.currentThread = thread
|
||||
}
|
||||
return thread, nil
|
||||
}
|
||||
|
||||
@ -84,10 +84,11 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||
}
|
||||
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
|
||||
tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, false), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
// Attach to an existing process with the given PID. Once attached, if
|
||||
@ -106,12 +107,12 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
|
||||
tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, false), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *Process) error {
|
||||
@ -165,7 +166,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
|
||||
}
|
||||
|
||||
if dbp.currentThread == nil {
|
||||
dbp.SwitchThread(tid)
|
||||
dbp.currentThread = dbp.threads[tid]
|
||||
}
|
||||
|
||||
return dbp.threads[tid], nil
|
||||
|
||||
@ -90,10 +90,11 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||
}
|
||||
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
|
||||
tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, false), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
// Attach to an existing process with the given PID. Once attached, if
|
||||
@ -112,7 +113,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
|
||||
tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
return nil, err
|
||||
@ -124,7 +125,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, false), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *Process) error {
|
||||
@ -222,7 +223,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
|
||||
os: new(OSSpecificDetails),
|
||||
}
|
||||
if dbp.currentThread == nil {
|
||||
dbp.SwitchThread(tid)
|
||||
dbp.currentThread = dbp.threads[tid]
|
||||
}
|
||||
return dbp.threads[tid], nil
|
||||
}
|
||||
|
||||
@ -78,11 +78,12 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
|
||||
dbp.pid = p.Pid
|
||||
dbp.childProcess = true
|
||||
|
||||
if err = dbp.initialize(argv0Go, []string{}); err != nil {
|
||||
tgt, err := dbp.initialize(argv0Go, []string{})
|
||||
if err != nil {
|
||||
dbp.Detach(true)
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, true), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *Process) error {
|
||||
@ -167,11 +168,12 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = dbp.initialize(exepath, []string{}); err != nil {
|
||||
tgt, err := dbp.initialize(exepath, []string{})
|
||||
if err != nil {
|
||||
dbp.Detach(true)
|
||||
return nil, err
|
||||
}
|
||||
return proc.NewTarget(dbp, true), nil
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
// kill kills the process.
|
||||
@ -223,7 +225,7 @@ func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, susp
|
||||
thread.os.hThread = hThread
|
||||
dbp.threads[threadID] = thread
|
||||
if dbp.currentThread == nil {
|
||||
dbp.SwitchThread(thread.ID)
|
||||
dbp.currentThread = dbp.threads[threadID]
|
||||
}
|
||||
if suspendNewThreads {
|
||||
_, err := _SuspendThread(thread.os.hThread)
|
||||
|
||||
@ -55,33 +55,6 @@ func (pe ProcessDetachedError) Error() string {
|
||||
return "detached from the process"
|
||||
}
|
||||
|
||||
// PostInitializationSetup handles all of the initialization procedures
|
||||
// that must happen after Delve creates or attaches to a process.
|
||||
func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
|
||||
entryPoint, err := p.EntryPoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, image := range p.BinInfo().Images {
|
||||
if image.loadErr != nil {
|
||||
return image.loadErr
|
||||
}
|
||||
}
|
||||
|
||||
g, _ := GetG(p.CurrentThread())
|
||||
p.SetSelectedGoroutine(g)
|
||||
|
||||
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
|
||||
createFatalThrowBreakpoint(p, writeBreakpoint)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindFileLocation returns the PC for a given file:line.
|
||||
// Assumes that `file` is normalized to lower case and '/' on Windows.
|
||||
func FindFileLocation(p Process, fileName string, lineno int) ([]uint64, error) {
|
||||
@ -175,16 +148,19 @@ func Continue(dbp *Target) error {
|
||||
// Make sure we clear internal breakpoints if we simultaneously receive a
|
||||
// manual stop request and hit a breakpoint.
|
||||
if dbp.CheckAndClearManualStopRequest() {
|
||||
dbp.StopReason = StopManual
|
||||
dbp.ClearInternalBreakpoints()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if dbp.CheckAndClearManualStopRequest() {
|
||||
dbp.StopReason = StopManual
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return nil
|
||||
}
|
||||
dbp.ClearAllGCache()
|
||||
trapthread, err := dbp.ContinueOnce()
|
||||
trapthread, stopReason, err := dbp.proc.ContinueOnce()
|
||||
dbp.StopReason = stopReason
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -232,6 +208,7 @@ func Continue(dbp *Target) error {
|
||||
if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
|
||||
return err
|
||||
}
|
||||
dbp.StopReason = StopHardcodedBreakpoint
|
||||
return conditionErrors(threads)
|
||||
case g == nil || dbp.fncallForG[g.ID] == nil:
|
||||
// a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo
|
||||
@ -272,6 +249,7 @@ func Continue(dbp *Target) error {
|
||||
if err := dbp.ClearInternalBreakpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
dbp.StopReason = StopNextFinished
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
case curbp.Active:
|
||||
@ -288,6 +266,7 @@ func Continue(dbp *Target) error {
|
||||
if curbp.Name == UnrecoveredPanic {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
}
|
||||
dbp.StopReason = StopBreakpoint
|
||||
return conditionErrors(threads)
|
||||
default:
|
||||
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
|
||||
@ -295,6 +274,7 @@ func Continue(dbp *Target) error {
|
||||
if callInjectionDone {
|
||||
// a call injection was finished, don't let a breakpoint with a failed
|
||||
// condition or a step breakpoint shadow this.
|
||||
dbp.StopReason = StopCallReturned
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
}
|
||||
@ -318,7 +298,7 @@ func conditionErrors(threads []Thread) error {
|
||||
// - a thread with onTriggeredInternalBreakpoint() == true
|
||||
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
|
||||
// - trapthread
|
||||
func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
|
||||
func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
|
||||
for _, th := range threads {
|
||||
if bp := th.Breakpoint(); bp.Active && bp.Internal {
|
||||
return dbp.SwitchThread(th.ThreadID())
|
||||
@ -339,7 +319,7 @@ func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
|
||||
// function is neither fnname1 or fnname2.
|
||||
// This function is used to step out of runtime.Breakpoint as well as
|
||||
// runtime.debugCallV1.
|
||||
func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string) error {
|
||||
func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error {
|
||||
for {
|
||||
if err := curthread.StepInstruction(); err != nil {
|
||||
return err
|
||||
@ -538,7 +518,7 @@ func StepInstruction(dbp *Target) (err error) {
|
||||
return err
|
||||
}
|
||||
if tg, _ := GetG(thread); tg != nil {
|
||||
dbp.SetSelectedGoroutine(tg)
|
||||
dbp.selectedGoroutine = tg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,9 +1,24 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Target represents the process being debugged.
|
||||
type Target struct {
|
||||
Process
|
||||
|
||||
proc ProcessInternal
|
||||
|
||||
// StopReason describes the reason why the target process is stopped.
|
||||
// A process could be stopped for multiple simultaneous reasons, in which
|
||||
// case only one will be reported.
|
||||
StopReason StopReason
|
||||
|
||||
// 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 *G
|
||||
|
||||
// fncallForG stores a mapping of current active function calls.
|
||||
fncallForG map[int]*callInjection
|
||||
|
||||
@ -16,19 +31,69 @@ type Target struct {
|
||||
gcache goroutineCache
|
||||
}
|
||||
|
||||
// StopReason describes the reason why the target process is stopped.
|
||||
// A process could be stopped for multiple simultaneous reasons, in which
|
||||
// case only one will be reported.
|
||||
type StopReason uint8
|
||||
|
||||
const (
|
||||
StopUnknown StopReason = iota
|
||||
StopLaunched // The process was just launched
|
||||
StopAttached // The debugger stopped the process after attaching
|
||||
StopExited // The target proces terminated
|
||||
StopBreakpoint // The target process hit one or more software breakpoints
|
||||
StopHardcodedBreakpoint // The target process hit a hardcoded breakpoint (for example runtime.Breakpoint())
|
||||
StopManual // A manual stop was requested
|
||||
StopNextFinished // The next/step/stepout command terminated
|
||||
StopCallReturned // An injected call commpleted
|
||||
)
|
||||
|
||||
// NewTargetConfig contains the configuration for a new Target object,
|
||||
type NewTargetConfig struct {
|
||||
Path string // path of the main executable
|
||||
DebugInfoDirs []string // Directories to search for split debug info
|
||||
WriteBreakpoint WriteBreakpointFn // Function to write a breakpoint to the target process
|
||||
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
|
||||
StopReason StopReason // Initial stop reason
|
||||
}
|
||||
|
||||
// NewTarget returns an initialized Target object.
|
||||
func NewTarget(p Process, disableAsyncPreempt bool) *Target {
|
||||
func NewTarget(p Process, cfg NewTargetConfig) (*Target, error) {
|
||||
entryPoint, err := p.EntryPoint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.BinInfo().LoadBinaryInfo(cfg.Path, entryPoint, cfg.DebugInfoDirs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, image := range p.BinInfo().Images {
|
||||
if image.loadErr != nil {
|
||||
return nil, image.loadErr
|
||||
}
|
||||
}
|
||||
|
||||
t := &Target{
|
||||
Process: p,
|
||||
proc: p.(ProcessInternal),
|
||||
fncallForG: make(map[int]*callInjection),
|
||||
StopReason: cfg.StopReason,
|
||||
}
|
||||
|
||||
g, _ := GetG(p.CurrentThread())
|
||||
t.selectedGoroutine = g
|
||||
|
||||
createUnrecoveredPanicBreakpoint(p, cfg.WriteBreakpoint)
|
||||
createFatalThrowBreakpoint(p, cfg.WriteBreakpoint)
|
||||
|
||||
t.gcache.init(p.BinInfo())
|
||||
|
||||
if disableAsyncPreempt {
|
||||
if cfg.DisableAsyncPreempt {
|
||||
setAsyncPreemptOff(t, 1)
|
||||
}
|
||||
|
||||
return t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// SupportsFunctionCalls returns whether or not the backend supports
|
||||
@ -54,7 +119,50 @@ func (t *Target) ClearAllGCache() {
|
||||
// Restarting of a normal process happens at a higher level (debugger.Restart).
|
||||
func (t *Target) Restart(from string) error {
|
||||
t.ClearAllGCache()
|
||||
return t.Process.Restart(from)
|
||||
err := t.proc.Restart(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.selectedGoroutine, _ = GetG(t.CurrentThread())
|
||||
if from != "" {
|
||||
t.StopReason = StopManual
|
||||
} else {
|
||||
t.StopReason = StopLaunched
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectedGoroutine returns the currently selected goroutine.
|
||||
func (t *Target) SelectedGoroutine() *G {
|
||||
return t.selectedGoroutine
|
||||
}
|
||||
|
||||
// SwitchGoroutine will change the selected and active goroutine.
|
||||
func (p *Target) SwitchGoroutine(g *G) error {
|
||||
if ok, err := p.Valid(); !ok {
|
||||
return err
|
||||
}
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
if g.Thread != nil {
|
||||
return p.SwitchThread(g.Thread.ThreadID())
|
||||
}
|
||||
p.selectedGoroutine = g
|
||||
return nil
|
||||
}
|
||||
|
||||
// SwitchThread will change the selected and active thread.
|
||||
func (p *Target) SwitchThread(tid int) error {
|
||||
if ok, err := p.Valid(); !ok {
|
||||
return err
|
||||
}
|
||||
if th, ok := p.FindThread(tid); ok {
|
||||
p.proc.SetCurrentThread(th)
|
||||
p.selectedGoroutine, _ = GetG(p.CurrentThread())
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("thread %d does not exist", tid)
|
||||
}
|
||||
|
||||
// Detach will detach the target from the underylying process.
|
||||
@ -65,5 +173,6 @@ func (t *Target) Detach(kill bool) error {
|
||||
if !kill && t.asyncPreemptChanged {
|
||||
setAsyncPreemptOff(t, t.asyncPreemptOff)
|
||||
}
|
||||
return t.Process.Detach(kill)
|
||||
t.StopReason = StopUnknown
|
||||
return t.proc.Detach(kill)
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ func (err *ErrNoSourceForPC) Error() string {
|
||||
// for an inlined function call. Everything works the same as normal except
|
||||
// when removing instructions belonging to inlined calls we also remove all
|
||||
// instructions belonging to the current inlined call.
|
||||
func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
||||
func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||
selg := dbp.SelectedGoroutine()
|
||||
curthread := dbp.CurrentThread()
|
||||
topframe, retframe, err := topframe(selg, curthread)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user