proc,service/debugger: introduce TargetGroup abstraction (#3030)
Introduces a new TargetGroup abstraction that can be used to manage multiple related targets. No actual management of child processes is implemented here, this is just a refactoring to make it possible to do that in the future. Updates #2551
This commit is contained in:
parent
1fe03e728c
commit
6f34add5db
@ -639,7 +639,7 @@ func (t *Target) SetWatchpoint(logicalID int, scope *EvalScope, expr string, wty
|
|||||||
|
|
||||||
func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind BreakpointKind, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
|
func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind BreakpointKind, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
|
||||||
if valid, err := t.Valid(); !valid {
|
if valid, err := t.Valid(); !valid {
|
||||||
recorded, _ := t.Recorded()
|
recorded, _ := t.recman.Recorded()
|
||||||
if !recorded {
|
if !recorded {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -744,7 +744,7 @@ func (bp *Breakpoint) canOverlap(kind BreakpointKind) bool {
|
|||||||
// ClearBreakpoint clears the breakpoint at addr.
|
// ClearBreakpoint clears the breakpoint at addr.
|
||||||
func (t *Target) ClearBreakpoint(addr uint64) error {
|
func (t *Target) ClearBreakpoint(addr uint64) error {
|
||||||
if valid, err := t.Valid(); !valid {
|
if valid, err := t.Valid(); !valid {
|
||||||
recorded, _ := t.Recorded()
|
recorded, _ := t.recman.Recorded()
|
||||||
if !recorded {
|
if !recorded {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -227,7 +227,9 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
|
|||||||
DebugInfoDirs: debugInfoDirs,
|
DebugInfoDirs: debugInfoDirs,
|
||||||
DisableAsyncPreempt: false,
|
DisableAsyncPreempt: false,
|
||||||
StopReason: proc.StopAttached,
|
StopReason: proc.StopAttached,
|
||||||
CanDump: false})
|
CanDump: false,
|
||||||
|
ContinueOnce: continueOnce,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinInfo will return the binary info.
|
// BinInfo will return the binary info.
|
||||||
@ -417,9 +419,7 @@ func (p *process) ClearInternalBreakpoints() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContinueOnce will always return an error because you
|
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
||||||
// cannot control execution of a core file.
|
|
||||||
func (p *process) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
|
||||||
return nil, proc.StopUnknown, ErrContinueCore
|
return nil, proc.StopUnknown, ErrContinueCore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -256,8 +256,9 @@ func TestCore(t *testing.T) {
|
|||||||
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
|
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
|
||||||
}
|
}
|
||||||
p := withCoreFile(t, "panic", "")
|
p := withCoreFile(t, "panic", "")
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
|
|
||||||
recorded, _ := p.Recorded()
|
recorded, _ := grp.Recorded()
|
||||||
if !recorded {
|
if !recorded {
|
||||||
t.Fatalf("expecting recorded to be true")
|
t.Fatalf("expecting recorded to be true")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,7 +146,8 @@ func (callCtx *callContext) doReturn(ret *Variable, err error) {
|
|||||||
// EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
|
// EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
|
||||||
// Because this can only be done in the current goroutine, unlike
|
// Because this can only be done in the current goroutine, unlike
|
||||||
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
|
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
|
||||||
func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
|
func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
|
||||||
|
t := grp.Selected
|
||||||
bi := t.BinInfo()
|
bi := t.BinInfo()
|
||||||
if !t.SupportsFunctionCalls() {
|
if !t.SupportsFunctionCalls() {
|
||||||
return errFuncCallUnsupportedBackend
|
return errFuncCallUnsupportedBackend
|
||||||
@ -201,7 +202,7 @@ func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig
|
|||||||
|
|
||||||
contReq, ok := <-continueRequest
|
contReq, ok := <-continueRequest
|
||||||
if contReq.cont {
|
if contReq.cont {
|
||||||
return t.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
return finishEvalExpressionWithCalls(t, g, contReq, ok)
|
return finishEvalExpressionWithCalls(t, g, contReq, ok)
|
||||||
|
|||||||
@ -709,7 +709,9 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
|
|||||||
DebugInfoDirs: debugInfoDirs,
|
DebugInfoDirs: debugInfoDirs,
|
||||||
DisableAsyncPreempt: runtime.GOOS == "darwin",
|
DisableAsyncPreempt: runtime.GOOS == "darwin",
|
||||||
StopReason: stopReason,
|
StopReason: stopReason,
|
||||||
CanDump: runtime.GOOS == "darwin"})
|
CanDump: runtime.GOOS == "darwin",
|
||||||
|
ContinueOnce: continueOnce,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Detach(true)
|
p.Detach(true)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -799,9 +801,11 @@ const (
|
|||||||
debugServerTargetExcBreakpoint = 0x96
|
debugServerTargetExcBreakpoint = 0x96
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContinueOnce will continue execution of the process until
|
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
||||||
// a breakpoint is hit or signal is received.
|
if len(procs) != 1 {
|
||||||
func (p *gdbProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
p := procs[0].(*gdbProcess)
|
||||||
if p.exited {
|
if p.exited {
|
||||||
return nil, proc.StopExited, proc.ErrProcessExited{Pid: p.conn.pid}
|
return nil, proc.StopExited, proc.ErrProcessExited{Pid: p.conn.pid}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(protest.RunTestsWithFixtures(m))
|
os.Exit(protest.RunTestsWithFixtures(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixture protest.Fixture)) {
|
func withTestRecording(name string, t testing.TB, fn func(grp *proc.TargetGroup, fixture protest.Fixture)) {
|
||||||
fixture := protest.BuildFixture(name, 0)
|
fixture := protest.BuildFixture(name, 0)
|
||||||
protest.MustHaveRecordingAllowed(t)
|
protest.MustHaveRecordingAllowed(t)
|
||||||
if path, _ := exec.LookPath("rr"); path == "" {
|
if path, _ := exec.LookPath("rr"); path == "" {
|
||||||
@ -36,9 +36,11 @@ func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixtur
|
|||||||
}
|
}
|
||||||
t.Logf("replaying %q", tracedir)
|
t.Logf("replaying %q", tracedir)
|
||||||
|
|
||||||
defer p.Detach(true)
|
grp := proc.NewGroup(p)
|
||||||
|
|
||||||
fn(p, fixture)
|
defer grp.Detach(true)
|
||||||
|
|
||||||
|
fn(grp, fixture)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertNoError(err error, t testing.TB, s string) {
|
func assertNoError(err error, t testing.TB, s string) {
|
||||||
@ -69,25 +71,26 @@ func setFunctionBreakpoint(p *proc.Target, t *testing.T, fname string) *proc.Bre
|
|||||||
|
|
||||||
func TestRestartAfterExit(t *testing.T) {
|
func TestRestartAfterExit(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestRecording("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestRecording("testnextprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
p := grp.Selected
|
||||||
setFunctionBreakpoint(p, t, "main.main")
|
setFunctionBreakpoint(p, t, "main.main")
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
loc, err := p.CurrentThread().Location()
|
loc, err := p.CurrentThread().Location()
|
||||||
assertNoError(err, t, "CurrentThread().Location()")
|
assertNoError(err, t, "CurrentThread().Location()")
|
||||||
err = p.Continue()
|
err = grp.Continue()
|
||||||
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
|
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
|
||||||
t.Fatalf("program did not exit: %v", err)
|
t.Fatalf("program did not exit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNoError(p.Restart(""), t, "Restart")
|
assertNoError(grp.Restart(""), t, "Restart")
|
||||||
|
|
||||||
assertNoError(p.Continue(), t, "Continue (after restart)")
|
assertNoError(grp.Continue(), t, "Continue (after restart)")
|
||||||
loc2, err := p.CurrentThread().Location()
|
loc2, err := p.CurrentThread().Location()
|
||||||
assertNoError(err, t, "CurrentThread().Location() (after restart)")
|
assertNoError(err, t, "CurrentThread().Location() (after restart)")
|
||||||
if loc2.Line != loc.Line {
|
if loc2.Line != loc.Line {
|
||||||
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
|
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
|
||||||
}
|
}
|
||||||
err = p.Continue()
|
err = grp.Continue()
|
||||||
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
|
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
|
||||||
t.Fatalf("program did not exit (after exit): %v", err)
|
t.Fatalf("program did not exit (after exit): %v", err)
|
||||||
}
|
}
|
||||||
@ -96,21 +99,22 @@ func TestRestartAfterExit(t *testing.T) {
|
|||||||
|
|
||||||
func TestRestartDuringStop(t *testing.T) {
|
func TestRestartDuringStop(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestRecording("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestRecording("testnextprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
p := grp.Selected
|
||||||
setFunctionBreakpoint(p, t, "main.main")
|
setFunctionBreakpoint(p, t, "main.main")
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
loc, err := p.CurrentThread().Location()
|
loc, err := p.CurrentThread().Location()
|
||||||
assertNoError(err, t, "CurrentThread().Location()")
|
assertNoError(err, t, "CurrentThread().Location()")
|
||||||
|
|
||||||
assertNoError(p.Restart(""), t, "Restart")
|
assertNoError(grp.Restart(""), t, "Restart")
|
||||||
|
|
||||||
assertNoError(p.Continue(), t, "Continue (after restart)")
|
assertNoError(grp.Continue(), t, "Continue (after restart)")
|
||||||
loc2, err := p.CurrentThread().Location()
|
loc2, err := p.CurrentThread().Location()
|
||||||
assertNoError(err, t, "CurrentThread().Location() (after restart)")
|
assertNoError(err, t, "CurrentThread().Location() (after restart)")
|
||||||
if loc2.Line != loc.Line {
|
if loc2.Line != loc.Line {
|
||||||
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
|
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
|
||||||
}
|
}
|
||||||
err = p.Continue()
|
err = grp.Continue()
|
||||||
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
|
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
|
||||||
t.Fatalf("program did not exit (after exit): %v", err)
|
t.Fatalf("program did not exit (after exit): %v", err)
|
||||||
}
|
}
|
||||||
@ -137,22 +141,23 @@ func setFileBreakpoint(p *proc.Target, t *testing.T, fixture protest.Fixture, li
|
|||||||
|
|
||||||
func TestReverseBreakpointCounts(t *testing.T) {
|
func TestReverseBreakpointCounts(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestRecording("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestRecording("bpcountstest", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
p := grp.Selected
|
||||||
endbp := setFileBreakpoint(p, t, fixture, 28)
|
endbp := setFileBreakpoint(p, t, fixture, 28)
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
loc, _ := p.CurrentThread().Location()
|
loc, _ := p.CurrentThread().Location()
|
||||||
if loc.PC != endbp.Addr {
|
if loc.PC != endbp.Addr {
|
||||||
t.Fatalf("did not reach end of main.main function: %s:%d (%#x)", loc.File, loc.Line, loc.PC)
|
t.Fatalf("did not reach end of main.main function: %s:%d (%#x)", loc.File, loc.Line, loc.PC)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.ClearBreakpoint(endbp.Addr)
|
p.ClearBreakpoint(endbp.Addr)
|
||||||
assertNoError(p.ChangeDirection(proc.Backward), t, "Switching to backward direction")
|
assertNoError(grp.ChangeDirection(proc.Backward), t, "Switching to backward direction")
|
||||||
bp := setFileBreakpoint(p, t, fixture, 12)
|
bp := setFileBreakpoint(p, t, fixture, 12)
|
||||||
startbp := setFileBreakpoint(p, t, fixture, 20)
|
startbp := setFileBreakpoint(p, t, fixture, 20)
|
||||||
|
|
||||||
countLoop:
|
countLoop:
|
||||||
for {
|
for {
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
loc, _ := p.CurrentThread().Location()
|
loc, _ := p.CurrentThread().Location()
|
||||||
switch loc.PC {
|
switch loc.PC {
|
||||||
case startbp.Addr:
|
case startbp.Addr:
|
||||||
@ -181,40 +186,41 @@ func TestReverseBreakpointCounts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPosition(p *proc.Target, t *testing.T) (when string, loc *proc.Location) {
|
func getPosition(grp *proc.TargetGroup, t *testing.T) (when string, loc *proc.Location) {
|
||||||
var err error
|
var err error
|
||||||
when, err = p.When()
|
when, err = grp.When()
|
||||||
assertNoError(err, t, "When")
|
assertNoError(err, t, "When")
|
||||||
loc, err = p.CurrentThread().Location()
|
loc, err = grp.Selected.CurrentThread().Location()
|
||||||
assertNoError(err, t, "Location")
|
assertNoError(err, t, "Location")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckpoints(t *testing.T) {
|
func TestCheckpoints(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestRecording("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestRecording("continuetestprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
p := grp.Selected
|
||||||
// Continues until start of main.main, record output of 'when'
|
// Continues until start of main.main, record output of 'when'
|
||||||
bp := setFunctionBreakpoint(p, t, "main.main")
|
bp := setFunctionBreakpoint(p, t, "main.main")
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
when0, loc0 := getPosition(p, t)
|
when0, loc0 := getPosition(grp, t)
|
||||||
t.Logf("when0: %q (%#x) %x", when0, loc0.PC, p.CurrentThread().ThreadID())
|
t.Logf("when0: %q (%#x) %x", when0, loc0.PC, p.CurrentThread().ThreadID())
|
||||||
|
|
||||||
// Create a checkpoint and check that the list of checkpoints reflects this
|
// Create a checkpoint and check that the list of checkpoints reflects this
|
||||||
cpid, err := p.Checkpoint("checkpoint1")
|
cpid, err := grp.Checkpoint("checkpoint1")
|
||||||
if cpid != 1 {
|
if cpid != 1 {
|
||||||
t.Errorf("unexpected checkpoint id %d", cpid)
|
t.Errorf("unexpected checkpoint id %d", cpid)
|
||||||
}
|
}
|
||||||
assertNoError(err, t, "Checkpoint")
|
assertNoError(err, t, "Checkpoint")
|
||||||
checkpoints, err := p.Checkpoints()
|
checkpoints, err := grp.Checkpoints()
|
||||||
assertNoError(err, t, "Checkpoints")
|
assertNoError(err, t, "Checkpoints")
|
||||||
if len(checkpoints) != 1 {
|
if len(checkpoints) != 1 {
|
||||||
t.Fatalf("wrong number of checkpoints %v (one expected)", checkpoints)
|
t.Fatalf("wrong number of checkpoints %v (one expected)", checkpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move forward with next, check that the output of 'when' changes
|
// Move forward with next, check that the output of 'when' changes
|
||||||
assertNoError(p.Next(), t, "First Next")
|
assertNoError(grp.Next(), t, "First Next")
|
||||||
assertNoError(p.Next(), t, "Second Next")
|
assertNoError(grp.Next(), t, "Second Next")
|
||||||
when1, loc1 := getPosition(p, t)
|
when1, loc1 := getPosition(grp, t)
|
||||||
t.Logf("when1: %q (%#x) %x", when1, loc1.PC, p.CurrentThread().ThreadID())
|
t.Logf("when1: %q (%#x) %x", when1, loc1.PC, p.CurrentThread().ThreadID())
|
||||||
if loc0.PC == loc1.PC {
|
if loc0.PC == loc1.PC {
|
||||||
t.Fatalf("next did not move process %#x", loc0.PC)
|
t.Fatalf("next did not move process %#x", loc0.PC)
|
||||||
@ -225,10 +231,10 @@ func TestCheckpoints(t *testing.T) {
|
|||||||
|
|
||||||
// Move back to checkpoint, check that the output of 'when' is the same as
|
// Move back to checkpoint, check that the output of 'when' is the same as
|
||||||
// what it was when we set the breakpoint
|
// what it was when we set the breakpoint
|
||||||
p.Restart(fmt.Sprintf("c%d", cpid))
|
grp.Restart(fmt.Sprintf("c%d", cpid))
|
||||||
g, _ := proc.FindGoroutine(p, 1)
|
g, _ := proc.FindGoroutine(p, 1)
|
||||||
p.SwitchGoroutine(g)
|
p.SwitchGoroutine(g)
|
||||||
when2, loc2 := getPosition(p, t)
|
when2, loc2 := getPosition(grp, t)
|
||||||
t.Logf("when2: %q (%#x) %x", when2, loc2.PC, p.CurrentThread().ThreadID())
|
t.Logf("when2: %q (%#x) %x", when2, loc2.PC, p.CurrentThread().ThreadID())
|
||||||
if loc2.PC != loc0.PC {
|
if loc2.PC != loc0.PC {
|
||||||
t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC)
|
t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC)
|
||||||
@ -238,9 +244,9 @@ func TestCheckpoints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move forward with next again, check that the output of 'when' matches
|
// Move forward with next again, check that the output of 'when' matches
|
||||||
assertNoError(p.Next(), t, "First Next")
|
assertNoError(grp.Next(), t, "First Next")
|
||||||
assertNoError(p.Next(), t, "Second Next")
|
assertNoError(grp.Next(), t, "Second Next")
|
||||||
when3, loc3 := getPosition(p, t)
|
when3, loc3 := getPosition(grp, t)
|
||||||
t.Logf("when3: %q (%#x)", when3, loc3.PC)
|
t.Logf("when3: %q (%#x)", when3, loc3.PC)
|
||||||
if loc3.PC != loc1.PC {
|
if loc3.PC != loc1.PC {
|
||||||
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc3.PC)
|
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc3.PC)
|
||||||
@ -253,12 +259,12 @@ func TestCheckpoints(t *testing.T) {
|
|||||||
// output of 'when' again
|
// output of 'when' again
|
||||||
err = p.ClearBreakpoint(bp.Addr)
|
err = p.ClearBreakpoint(bp.Addr)
|
||||||
assertNoError(err, t, "ClearBreakpoint")
|
assertNoError(err, t, "ClearBreakpoint")
|
||||||
p.Restart(fmt.Sprintf("c%d", cpid))
|
grp.Restart(fmt.Sprintf("c%d", cpid))
|
||||||
g, _ = proc.FindGoroutine(p, 1)
|
g, _ = proc.FindGoroutine(p, 1)
|
||||||
p.SwitchGoroutine(g)
|
p.SwitchGoroutine(g)
|
||||||
assertNoError(p.Next(), t, "First Next")
|
assertNoError(grp.Next(), t, "First Next")
|
||||||
assertNoError(p.Next(), t, "Second Next")
|
assertNoError(grp.Next(), t, "Second Next")
|
||||||
when4, loc4 := getPosition(p, t)
|
when4, loc4 := getPosition(grp, t)
|
||||||
t.Logf("when4: %q (%#x)", when4, loc4.PC)
|
t.Logf("when4: %q (%#x)", when4, loc4.PC)
|
||||||
if loc4.PC != loc1.PC {
|
if loc4.PC != loc1.PC {
|
||||||
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc4.PC)
|
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc4.PC)
|
||||||
@ -268,8 +274,8 @@ func TestCheckpoints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete checkpoint, check that the list of checkpoints is updated
|
// Delete checkpoint, check that the list of checkpoints is updated
|
||||||
assertNoError(p.ClearCheckpoint(cpid), t, "ClearCheckpoint")
|
assertNoError(grp.ClearCheckpoint(cpid), t, "ClearCheckpoint")
|
||||||
checkpoints, err = p.Checkpoints()
|
checkpoints, err = grp.Checkpoints()
|
||||||
assertNoError(err, t, "Checkpoints")
|
assertNoError(err, t, "Checkpoints")
|
||||||
if len(checkpoints) != 0 {
|
if len(checkpoints) != 0 {
|
||||||
t.Fatalf("wrong number of checkpoints %v (zero expected)", checkpoints)
|
t.Fatalf("wrong number of checkpoints %v (zero expected)", checkpoints)
|
||||||
@ -280,12 +286,13 @@ func TestCheckpoints(t *testing.T) {
|
|||||||
func TestIssue1376(t *testing.T) {
|
func TestIssue1376(t *testing.T) {
|
||||||
// Backward Continue should terminate when it encounters the start of the process.
|
// Backward Continue should terminate when it encounters the start of the process.
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestRecording("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestRecording("continuetestprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
p := grp.Selected
|
||||||
bp := setFunctionBreakpoint(p, t, "main.main")
|
bp := setFunctionBreakpoint(p, t, "main.main")
|
||||||
assertNoError(p.Continue(), t, "Continue (forward)")
|
assertNoError(grp.Continue(), t, "Continue (forward)")
|
||||||
err := p.ClearBreakpoint(bp.Addr)
|
err := p.ClearBreakpoint(bp.Addr)
|
||||||
assertNoError(err, t, "ClearBreakpoint")
|
assertNoError(err, t, "ClearBreakpoint")
|
||||||
assertNoError(p.ChangeDirection(proc.Backward), t, "Switching to backward direction")
|
assertNoError(grp.ChangeDirection(proc.Backward), t, "Switching to backward direction")
|
||||||
assertNoError(p.Continue(), t, "Continue (backward)")
|
assertNoError(grp.Continue(), t, "Continue (backward)")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,6 @@ type ProcessInternal interface {
|
|||||||
// ErrProcessExited or ErrProcessDetached).
|
// ErrProcessExited or ErrProcessDetached).
|
||||||
Valid() (bool, error)
|
Valid() (bool, error)
|
||||||
Detach(bool) error
|
Detach(bool) error
|
||||||
ContinueOnce(*ContinueOnceContext) (trapthread Thread, stopReason StopReason, err error)
|
|
||||||
|
|
||||||
// RequestManualStop attempts to stop all the process' threads.
|
// RequestManualStop attempts to stop all the process' threads.
|
||||||
RequestManualStop(cctx *ContinueOnceContext) error
|
RequestManualStop(cctx *ContinueOnceContext) error
|
||||||
|
|||||||
@ -172,9 +172,11 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
|
|||||||
return dbp.memthread.clearSoftwareBreakpoint(bp)
|
return dbp.memthread.clearSoftwareBreakpoint(bp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContinueOnce will continue the target until it stops.
|
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
||||||
// This could be the result of a breakpoint or signal.
|
if len(procs) != 1 {
|
||||||
func (dbp *nativeProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
dbp := procs[0].(*nativeProcess)
|
||||||
if dbp.exited {
|
if dbp.exited {
|
||||||
return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid}
|
return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid}
|
||||||
}
|
}
|
||||||
@ -253,8 +255,9 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc
|
|||||||
// See: https://go-review.googlesource.com/c/go/+/208126
|
// See: https://go-review.googlesource.com/c/go/+/208126
|
||||||
DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"),
|
DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"),
|
||||||
|
|
||||||
StopReason: stopReason,
|
StopReason: stopReason,
|
||||||
CanDump: runtime.GOOS == "linux" || runtime.GOOS == "windows",
|
CanDump: runtime.GOOS == "linux" || runtime.GOOS == "windows",
|
||||||
|
ContinueOnce: continueOnce,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
42
pkg/proc/proc_export_test.go
Normal file
42
pkg/proc/proc_export_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package proc
|
||||||
|
|
||||||
|
// Wrapper functions so that most tests proc_test.go don't need to worry
|
||||||
|
// about TargetGroup when they just need to resume a single process.
|
||||||
|
|
||||||
|
func newGroupTransient(tgt *Target) *TargetGroup {
|
||||||
|
grp := NewGroup(tgt)
|
||||||
|
tgt.partOfGroup = false
|
||||||
|
return grp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) Detach(kill bool) error {
|
||||||
|
return tgt.detach(kill)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) Continue() error {
|
||||||
|
return newGroupTransient(tgt).Continue()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) Next() error {
|
||||||
|
return newGroupTransient(tgt).Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) Step() error {
|
||||||
|
return newGroupTransient(tgt).Step()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) StepOut() error {
|
||||||
|
return newGroupTransient(tgt).StepOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) ChangeDirection(dir Direction) error {
|
||||||
|
return tgt.recman.ChangeDirection(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) StepInstruction() error {
|
||||||
|
return newGroupTransient(tgt).StepInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgt *Target) Recorded() (bool, string) {
|
||||||
|
return tgt.recman.Recorded()
|
||||||
|
}
|
||||||
@ -272,16 +272,17 @@ func findFileLocation(p *proc.Target, t *testing.T, file string, lineno int) uin
|
|||||||
func TestHalt(t *testing.T) {
|
func TestHalt(t *testing.T) {
|
||||||
stopChan := make(chan interface{}, 1)
|
stopChan := make(chan interface{}, 1)
|
||||||
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
setFunctionBreakpoint(p, t, "main.loop")
|
setFunctionBreakpoint(p, t, "main.loop")
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
resumeChan := make(chan struct{}, 1)
|
resumeChan := make(chan struct{}, 1)
|
||||||
go func() {
|
go func() {
|
||||||
<-resumeChan
|
<-resumeChan
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
stopChan <- p.RequestManualStop()
|
stopChan <- grp.RequestManualStop()
|
||||||
}()
|
}()
|
||||||
p.ResumeNotify(resumeChan)
|
grp.ResumeNotify(resumeChan)
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
retVal := <-stopChan
|
retVal := <-stopChan
|
||||||
|
|
||||||
if err, ok := retVal.(error); ok && err != nil {
|
if err, ok := retVal.(error); ok && err != nil {
|
||||||
@ -2064,6 +2065,7 @@ func TestCmdLineArgs(t *testing.T) {
|
|||||||
func TestIssue462(t *testing.T) {
|
func TestIssue462(t *testing.T) {
|
||||||
skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
|
skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
|
||||||
withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
go func() {
|
go func() {
|
||||||
// Wait for program to start listening.
|
// Wait for program to start listening.
|
||||||
for {
|
for {
|
||||||
@ -2075,10 +2077,10 @@ func TestIssue462(t *testing.T) {
|
|||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
_, err := proc.ThreadStacktrace(p.CurrentThread(), 40)
|
_, err := proc.ThreadStacktrace(p.CurrentThread(), 40)
|
||||||
assertNoError(err, t, "Stacktrace()")
|
assertNoError(err, t, "Stacktrace()")
|
||||||
})
|
})
|
||||||
@ -2638,22 +2640,23 @@ func TestNextBreakpoint(t *testing.T) {
|
|||||||
func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
|
func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
p.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints
|
grp := proc.NewGroup(p)
|
||||||
|
grp.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints
|
||||||
bp := setFileBreakpoint(p, t, fixture.Source, 34)
|
bp := setFileBreakpoint(p, t, fixture.Source, 34)
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
p.ClearBreakpoint(bp.Addr)
|
p.ClearBreakpoint(bp.Addr)
|
||||||
|
|
||||||
// Next should be interrupted by a tracepoint on the same goroutine.
|
// Next should be interrupted by a tracepoint on the same goroutine.
|
||||||
bp = setFileBreakpoint(p, t, fixture.Source, 14)
|
bp = setFileBreakpoint(p, t, fixture.Source, 14)
|
||||||
bp.Logical.Tracepoint = true
|
bp.Logical.Tracepoint = true
|
||||||
assertNoError(p.Next(), t, "Next()")
|
assertNoError(grp.Next(), t, "Next()")
|
||||||
assertLineNumber(p, t, 14, "wrong line number")
|
assertLineNumber(p, t, 14, "wrong line number")
|
||||||
if !p.Breakpoints().HasSteppingBreakpoints() {
|
if !p.Breakpoints().HasSteppingBreakpoints() {
|
||||||
t.Fatal("does not have internal breakpoints after hitting tracepoint on same goroutine")
|
t.Fatal("does not have internal breakpoints after hitting tracepoint on same goroutine")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue to complete next.
|
// Continue to complete next.
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
assertLineNumber(p, t, 35, "wrong line number")
|
assertLineNumber(p, t, 35, "wrong line number")
|
||||||
if p.Breakpoints().HasSteppingBreakpoints() {
|
if p.Breakpoints().HasSteppingBreakpoints() {
|
||||||
t.Fatal("has internal breakpoints after completing next")
|
t.Fatal("has internal breakpoints after completing next")
|
||||||
@ -3713,17 +3716,18 @@ func TestIssue1101(t *testing.T) {
|
|||||||
|
|
||||||
func TestIssue1145(t *testing.T) {
|
func TestIssue1145(t *testing.T) {
|
||||||
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
setFileBreakpoint(p, t, fixture.Source, 18)
|
setFileBreakpoint(p, t, fixture.Source, 18)
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
resumeChan := make(chan struct{}, 1)
|
resumeChan := make(chan struct{}, 1)
|
||||||
p.ResumeNotify(resumeChan)
|
grp.ResumeNotify(resumeChan)
|
||||||
go func() {
|
go func() {
|
||||||
<-resumeChan
|
<-resumeChan
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
assertNoError(p.Next(), t, "Next()")
|
assertNoError(grp.Next(), t, "Next()")
|
||||||
if p.Breakpoints().HasSteppingBreakpoints() {
|
if p.Breakpoints().HasSteppingBreakpoints() {
|
||||||
t.Fatal("has internal breakpoints after manual stop request")
|
t.Fatal("has internal breakpoints after manual stop request")
|
||||||
}
|
}
|
||||||
@ -3732,18 +3736,19 @@ func TestIssue1145(t *testing.T) {
|
|||||||
|
|
||||||
func TestHaltKeepsSteppingBreakpoints(t *testing.T) {
|
func TestHaltKeepsSteppingBreakpoints(t *testing.T) {
|
||||||
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
p.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints
|
grp := proc.NewGroup(p)
|
||||||
|
grp.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints
|
||||||
setFileBreakpoint(p, t, fixture.Source, 18)
|
setFileBreakpoint(p, t, fixture.Source, 18)
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
resumeChan := make(chan struct{}, 1)
|
resumeChan := make(chan struct{}, 1)
|
||||||
p.ResumeNotify(resumeChan)
|
grp.ResumeNotify(resumeChan)
|
||||||
go func() {
|
go func() {
|
||||||
<-resumeChan
|
<-resumeChan
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
assertNoError(p.Next(), t, "Next()")
|
assertNoError(grp.Next(), t, "Next()")
|
||||||
if !p.Breakpoints().HasSteppingBreakpoints() {
|
if !p.Breakpoints().HasSteppingBreakpoints() {
|
||||||
t.Fatal("does not have internal breakpoints after manual stop request")
|
t.Fatal("does not have internal breakpoints after manual stop request")
|
||||||
}
|
}
|
||||||
@ -4346,11 +4351,12 @@ func TestIssue1374(t *testing.T) {
|
|||||||
// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
|
// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
|
||||||
protest.MustSupportFunctionCalls(t, testBackend)
|
protest.MustSupportFunctionCalls(t, testBackend)
|
||||||
withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
setFileBreakpoint(p, t, fixture.Source, 7)
|
setFileBreakpoint(p, t, fixture.Source, 7)
|
||||||
assertNoError(p.Continue(), t, "First Continue")
|
assertNoError(grp.Continue(), t, "First Continue")
|
||||||
assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),")
|
assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),")
|
||||||
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call")
|
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call")
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
if _, isexited := err.(proc.ErrProcessExited); !isexited {
|
if _, isexited := err.(proc.ErrProcessExited); !isexited {
|
||||||
regs, _ := p.CurrentThread().Registers()
|
regs, _ := p.CurrentThread().Registers()
|
||||||
f, l, _ := p.BinInfo().PCToLine(regs.PC())
|
f, l, _ := p.BinInfo().PCToLine(regs.PC())
|
||||||
@ -4577,14 +4583,15 @@ func TestCallConcurrent(t *testing.T) {
|
|||||||
skipOn(t, "broken", "freebsd")
|
skipOn(t, "broken", "freebsd")
|
||||||
protest.MustSupportFunctionCalls(t, testBackend)
|
protest.MustSupportFunctionCalls(t, testBackend)
|
||||||
withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
bp := setFileBreakpoint(p, t, fixture.Source, 24)
|
bp := setFileBreakpoint(p, t, fixture.Source, 24)
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
//_, err := p.ClearBreakpoint(bp.Addr)
|
//_, err := p.ClearBreakpoint(bp.Addr)
|
||||||
//assertNoError(err, t, "ClearBreakpoint() returned an error")
|
//assertNoError(err, t, "ClearBreakpoint() returned an error")
|
||||||
|
|
||||||
gid1 := p.SelectedGoroutine().ID
|
gid1 := p.SelectedGoroutine().ID
|
||||||
t.Logf("starting injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
|
t.Logf("starting injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
|
||||||
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "Foo(10, 1)", normalLoadConfig, false), t, "EvalExpressionWithCalls()")
|
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "Foo(10, 1)", normalLoadConfig, false), t, "EvalExpressionWithCalls()")
|
||||||
|
|
||||||
returned := testCallConcurrentCheckReturns(p, t, gid1, -1)
|
returned := testCallConcurrentCheckReturns(p, t, gid1, -1)
|
||||||
|
|
||||||
@ -4599,7 +4606,7 @@ func TestCallConcurrent(t *testing.T) {
|
|||||||
|
|
||||||
gid2 := p.SelectedGoroutine().ID
|
gid2 := p.SelectedGoroutine().ID
|
||||||
t.Logf("starting second injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
|
t.Logf("starting second injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
|
||||||
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "Foo(10, 2)", normalLoadConfig, false), t, "EvalExpressioniWithCalls")
|
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "Foo(10, 2)", normalLoadConfig, false), t, "EvalExpressioniWithCalls")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
returned += testCallConcurrentCheckReturns(p, t, gid1, gid2)
|
returned += testCallConcurrentCheckReturns(p, t, gid1, gid2)
|
||||||
@ -4607,10 +4614,10 @@ func TestCallConcurrent(t *testing.T) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
t.Logf("Continuing... %d", returned)
|
t.Logf("Continuing... %d", returned)
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Continue()
|
grp.Continue()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4964,8 +4971,9 @@ func TestIssue1925(t *testing.T) {
|
|||||||
// altering the state of the target process.
|
// altering the state of the target process.
|
||||||
protest.MustSupportFunctionCalls(t, testBackend)
|
protest.MustSupportFunctionCalls(t, testBackend)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
grp := proc.NewGroup(p)
|
||||||
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
|
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call")
|
||||||
t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc)
|
t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc)
|
||||||
if loc := p.SelectedGoroutine().CurrentLoc; loc.File != fixture.Source {
|
if loc := p.SelectedGoroutine().CurrentLoc; loc.File != fixture.Source {
|
||||||
t.Errorf("wrong location for selected goroutine after call: %s:%d", loc.File, loc.Line)
|
t.Errorf("wrong location for selected goroutine after call: %s:%d", loc.File, loc.Line)
|
||||||
@ -5059,31 +5067,32 @@ func TestStepoutOneliner(t *testing.T) {
|
|||||||
func TestRequestManualStopWhileStopped(t *testing.T) {
|
func TestRequestManualStopWhileStopped(t *testing.T) {
|
||||||
// Requesting a manual stop while stopped shouldn't cause problems (issue #2138).
|
// Requesting a manual stop while stopped shouldn't cause problems (issue #2138).
|
||||||
withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
resumed := make(chan struct{})
|
resumed := make(chan struct{})
|
||||||
setFileBreakpoint(p, t, fixture.Source, 8)
|
setFileBreakpoint(p, t, fixture.Source, 8)
|
||||||
assertNoError(p.Continue(), t, "Continue() 1")
|
assertNoError(grp.Continue(), t, "Continue() 1")
|
||||||
p.ResumeNotify(resumed)
|
grp.ResumeNotify(resumed)
|
||||||
go func() {
|
go func() {
|
||||||
<-resumed
|
<-resumed
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
}()
|
}()
|
||||||
t.Logf("at time.Sleep call")
|
t.Logf("at time.Sleep call")
|
||||||
assertNoError(p.Continue(), t, "Continue() 2")
|
assertNoError(grp.Continue(), t, "Continue() 2")
|
||||||
t.Logf("manually stopped")
|
t.Logf("manually stopped")
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
|
|
||||||
resumed = make(chan struct{})
|
resumed = make(chan struct{})
|
||||||
p.ResumeNotify(resumed)
|
grp.ResumeNotify(resumed)
|
||||||
go func() {
|
go func() {
|
||||||
<-resumed
|
<-resumed
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
}()
|
}()
|
||||||
t.Logf("resuming sleep")
|
t.Logf("resuming sleep")
|
||||||
assertNoError(p.Continue(), t, "Continue() 3")
|
assertNoError(grp.Continue(), t, "Continue() 3")
|
||||||
t.Logf("done")
|
t.Logf("done")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -5546,9 +5555,10 @@ func TestWatchpointCounts(t *testing.T) {
|
|||||||
func TestManualStopWhileStopped(t *testing.T) {
|
func TestManualStopWhileStopped(t *testing.T) {
|
||||||
// Checks that RequestManualStop sent to a stopped thread does not cause the target process to die.
|
// Checks that RequestManualStop sent to a stopped thread does not cause the target process to die.
|
||||||
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
asyncCont := func(done chan struct{}) {
|
asyncCont := func(done chan struct{}) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
t.Logf("%v\n", err)
|
t.Logf("%v\n", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -5571,9 +5581,9 @@ func TestManualStopWhileStopped(t *testing.T) {
|
|||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go asyncCont(done)
|
go asyncCont(done)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
@ -5581,11 +5591,11 @@ func TestManualStopWhileStopped(t *testing.T) {
|
|||||||
t.Logf("Continue %d (fast)", i)
|
t.Logf("Continue %d (fast)", i)
|
||||||
rch := make(chan struct{})
|
rch := make(chan struct{})
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
p.ResumeNotify(rch)
|
grp.ResumeNotify(rch)
|
||||||
go asyncCont(done)
|
go asyncCont(done)
|
||||||
<-rch
|
<-rch
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -5865,6 +5875,8 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
|
|||||||
withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
mainfn := p.BinInfo().LookupFunc["main.main"]
|
mainfn := p.BinInfo().LookupFunc["main.main"]
|
||||||
|
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
|
|
||||||
// Find JNZ instruction on line :14
|
// Find JNZ instruction on line :14
|
||||||
var addr uint64
|
var addr uint64
|
||||||
text, err := proc.Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End)
|
text, err := proc.Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End)
|
||||||
@ -5886,7 +5898,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
|
|||||||
assertNoError(err, t, "SetBreakpoint")
|
assertNoError(err, t, "SetBreakpoint")
|
||||||
|
|
||||||
// Continue to breakpoint
|
// Continue to breakpoint
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
assertLineNumber(p, t, 14, "expected line :14")
|
assertLineNumber(p, t, 14, "expected line :14")
|
||||||
|
|
||||||
// Save RFLAGS register
|
// Save RFLAGS register
|
||||||
@ -5894,7 +5906,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
|
|||||||
t.Logf("rflags before = %#x", rflagsBeforeCall)
|
t.Logf("rflags before = %#x", rflagsBeforeCall)
|
||||||
|
|
||||||
// Inject call to main.g()
|
// Inject call to main.g()
|
||||||
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "g()", normalLoadConfig, true), t, "Call")
|
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "g()", normalLoadConfig, true), t, "Call")
|
||||||
|
|
||||||
// Check RFLAGS register after the call
|
// Check RFLAGS register after the call
|
||||||
rflagsAfterCall := p.BinInfo().Arch.RegistersToDwarfRegisters(0, getRegisters(p, t)).Uint64Val(regnum.AMD64_Rflags)
|
rflagsAfterCall := p.BinInfo().Arch.RegistersToDwarfRegisters(0, getRegisters(p, t)).Uint64Val(regnum.AMD64_Rflags)
|
||||||
@ -5905,7 +5917,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Single step and check where we end up
|
// Single step and check where we end up
|
||||||
assertNoError(p.Step(), t, "Step()")
|
assertNoError(grp.Step(), t, "Step()")
|
||||||
assertLineNumber(p, t, 17, "expected line :17") // since we passed "0" as argument we should be going into the false branch at line :17
|
assertLineNumber(p, t, 17, "expected line :17") // since we passed "0" as argument we should be going into the false branch at line :17
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,24 +37,25 @@ func TestIssue419(t *testing.T) {
|
|||||||
|
|
||||||
// SIGINT directed at the inferior should be passed along not swallowed by delve
|
// SIGINT directed at the inferior should be passed along not swallowed by delve
|
||||||
withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
setFunctionBreakpoint(p, t, "main.main")
|
setFunctionBreakpoint(p, t, "main.main")
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
resumeChan := make(chan struct{}, 1)
|
resumeChan := make(chan struct{}, 1)
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
<-resumeChan
|
<-resumeChan
|
||||||
if p.Pid() <= 0 {
|
if p.Pid() <= 0 {
|
||||||
// if we don't stop the inferior the test will never finish
|
// if we don't stop the inferior the test will never finish
|
||||||
p.RequestManualStop()
|
grp.RequestManualStop()
|
||||||
err := p.Detach(true)
|
err := grp.Detach(true)
|
||||||
errChan <- errIssue419{pid: p.Pid(), err: err}
|
errChan <- errIssue419{pid: p.Pid(), err: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := syscall.Kill(p.Pid(), syscall.SIGINT)
|
err := syscall.Kill(p.Pid(), syscall.SIGINT)
|
||||||
errChan <- errIssue419{pid: p.Pid(), err: err}
|
errChan <- errIssue419{pid: p.Pid(), err: err}
|
||||||
}()
|
}()
|
||||||
p.ResumeNotify(resumeChan)
|
grp.ResumeNotify(resumeChan)
|
||||||
errChan <- p.Continue()
|
errChan <- grp.Continue()
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
|
|||||||
@ -72,7 +72,7 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi
|
|||||||
retbreaklet.watchpoint = watchpoint
|
retbreaklet.watchpoint = watchpoint
|
||||||
retbreaklet.callback = woos
|
retbreaklet.callback = woos
|
||||||
|
|
||||||
if recorded, _ := t.Recorded(); recorded && retframe.Current.Fn != nil {
|
if recorded, _ := t.recman.Recorded(); recorded && retframe.Current.Fn != nil {
|
||||||
// Must also set a breakpoint on the call instruction immediately
|
// Must also set a breakpoint on the call instruction immediately
|
||||||
// preceding retframe.Current.PC, because the watchpoint could also go out
|
// preceding retframe.Current.PC, because the watchpoint could also go out
|
||||||
// of scope while we are running backwards.
|
// of scope while we are running backwards.
|
||||||
|
|||||||
@ -36,10 +36,10 @@ const (
|
|||||||
// Target represents the process being debugged.
|
// Target represents the process being debugged.
|
||||||
type Target struct {
|
type Target struct {
|
||||||
Process
|
Process
|
||||||
RecordingManipulation
|
|
||||||
|
|
||||||
proc ProcessInternal
|
proc ProcessInternal
|
||||||
recman RecordingManipulationInternal
|
recman RecordingManipulationInternal
|
||||||
|
continueOnce ContinueOnceFunc
|
||||||
|
|
||||||
pid int
|
pid int
|
||||||
|
|
||||||
@ -51,10 +51,6 @@ type Target struct {
|
|||||||
// CanDump is true if core dumping is supported.
|
// CanDump is true if core dumping is supported.
|
||||||
CanDump bool
|
CanDump bool
|
||||||
|
|
||||||
// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
|
|
||||||
// will keep the stepping breakpoints instead of clearing them.
|
|
||||||
KeepSteppingBreakpoints KeepSteppingBreakpoints
|
|
||||||
|
|
||||||
// currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected.
|
// currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected.
|
||||||
currentThread Thread
|
currentThread Thread
|
||||||
|
|
||||||
@ -84,7 +80,7 @@ type Target struct {
|
|||||||
fakeMemoryRegistry []*compositeMemory
|
fakeMemoryRegistry []*compositeMemory
|
||||||
fakeMemoryRegistryMap map[string]*compositeMemory
|
fakeMemoryRegistryMap map[string]*compositeMemory
|
||||||
|
|
||||||
cctx *ContinueOnceContext
|
partOfGroup bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeepSteppingBreakpoints uint8
|
type KeepSteppingBreakpoints uint8
|
||||||
@ -151,6 +147,8 @@ const (
|
|||||||
StopWatchpoint // The target process hit one or more watchpoints
|
StopWatchpoint // The target process hit one or more watchpoints
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ContinueOnceFunc func([]ProcessInternal, *ContinueOnceContext) (trapthread Thread, stopReason StopReason, err error)
|
||||||
|
|
||||||
// NewTargetConfig contains the configuration for a new Target object,
|
// NewTargetConfig contains the configuration for a new Target object,
|
||||||
type NewTargetConfig struct {
|
type NewTargetConfig struct {
|
||||||
Path string // path of the main executable
|
Path string // path of the main executable
|
||||||
@ -158,6 +156,7 @@ type NewTargetConfig struct {
|
|||||||
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
|
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
|
||||||
StopReason StopReason // Initial stop reason
|
StopReason StopReason // Initial stop reason
|
||||||
CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap)
|
CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap)
|
||||||
|
ContinueOnce ContinueOnceFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableAsyncPreemptEnv returns a process environment (like os.Environ)
|
// DisableAsyncPreemptEnv returns a process environment (like os.Environ)
|
||||||
@ -200,7 +199,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
|
|||||||
currentThread: currentThread,
|
currentThread: currentThread,
|
||||||
CanDump: cfg.CanDump,
|
CanDump: cfg.CanDump,
|
||||||
pid: pid,
|
pid: pid,
|
||||||
cctx: &ContinueOnceContext{},
|
continueOnce: cfg.ContinueOnce,
|
||||||
}
|
}
|
||||||
|
|
||||||
if recman, ok := p.(RecordingManipulationInternal); ok {
|
if recman, ok := p.(RecordingManipulationInternal); ok {
|
||||||
@ -208,7 +207,6 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
|
|||||||
} else {
|
} else {
|
||||||
t.recman = &dummyRecordingManipulation{}
|
t.recman = &dummyRecordingManipulation{}
|
||||||
}
|
}
|
||||||
t.RecordingManipulation = t.recman
|
|
||||||
|
|
||||||
g, _ := GetG(currentThread)
|
g, _ := GetG(currentThread)
|
||||||
t.selectedGoroutine = g
|
t.selectedGoroutine = g
|
||||||
@ -281,12 +279,18 @@ func (t *Target) ClearCaches() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart will start the process over from the location specified by the "from" locspec.
|
// Restart will start the process group over from the location specified by the "from" locspec.
|
||||||
// This is only useful for recorded targets.
|
// This is only useful for recorded targets.
|
||||||
// Restarting of a normal process happens at a higher level (debugger.Restart).
|
// Restarting of a normal process happens at a higher level (debugger.Restart).
|
||||||
func (t *Target) Restart(from string) error {
|
func (grp *TargetGroup) Restart(from string) error {
|
||||||
t.ClearCaches()
|
if len(grp.targets) != 1 {
|
||||||
currentThread, err := t.recman.Restart(t.cctx, from)
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
t.ClearCaches()
|
||||||
|
}
|
||||||
|
t := grp.Selected
|
||||||
|
currentThread, err := t.recman.Restart(grp.cctx, from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -333,11 +337,11 @@ func (p *Target) SwitchThread(tid int) error {
|
|||||||
return fmt.Errorf("thread %d does not exist", tid)
|
return fmt.Errorf("thread %d does not exist", tid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detach will detach the target from the underylying process.
|
// detach will detach the target from the underylying process.
|
||||||
// This means the debugger will no longer receive events from the process
|
// This means the debugger will no longer receive events from the process
|
||||||
// we were previously debugging.
|
// we were previously debugging.
|
||||||
// If kill is true then the process will be killed when we detach.
|
// If kill is true then the process will be killed when we detach.
|
||||||
func (t *Target) Detach(kill bool) error {
|
func (t *Target) detach(kill bool) error {
|
||||||
if !kill {
|
if !kill {
|
||||||
if t.asyncPreemptChanged {
|
if t.asyncPreemptChanged {
|
||||||
setAsyncPreemptOff(t, t.asyncPreemptOff)
|
setAsyncPreemptOff(t, t.asyncPreemptOff)
|
||||||
@ -475,17 +479,17 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResumeNotify specifies a channel that will be closed the next time
|
// ResumeNotify specifies a channel that will be closed the next time
|
||||||
// Continue finishes resuming the target.
|
// Continue finishes resuming the targets.
|
||||||
func (t *Target) ResumeNotify(ch chan<- struct{}) {
|
func (t *TargetGroup) ResumeNotify(ch chan<- struct{}) {
|
||||||
t.cctx.ResumeChan = ch
|
t.cctx.ResumeChan = ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestManualStop attempts to stop all the process' threads.
|
// RequestManualStop attempts to stop all the processes' threads.
|
||||||
func (t *Target) RequestManualStop() error {
|
func (t *TargetGroup) RequestManualStop() error {
|
||||||
t.cctx.StopMu.Lock()
|
t.cctx.StopMu.Lock()
|
||||||
defer t.cctx.StopMu.Unlock()
|
defer t.cctx.StopMu.Unlock()
|
||||||
t.cctx.manualStopRequested = true
|
t.cctx.manualStopRequested = true
|
||||||
return t.proc.RequestManualStop(t.cctx)
|
return t.Selected.proc.RequestManualStop(t.cctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@ -26,27 +26,32 @@ func (err *ErrNoSourceForPC) Error() string {
|
|||||||
return fmt.Sprintf("no source for PC %#x", err.pc)
|
return fmt.Sprintf("no source for PC %#x", err.pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next continues execution until the next source line.
|
// Next resumes the processes in the group, continuing the selected target
|
||||||
func (dbp *Target) Next() (err error) {
|
// until the next source line.
|
||||||
if _, err := dbp.Valid(); err != nil {
|
func (grp *TargetGroup) Next() (err error) {
|
||||||
|
if _, err := grp.Valid(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if dbp.Breakpoints().HasSteppingBreakpoints() {
|
if grp.HasSteppingBreakpoints() {
|
||||||
return fmt.Errorf("next while nexting")
|
return fmt.Errorf("next while nexting")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = next(dbp, false, false); err != nil {
|
if err = next(grp.Selected, false, false); err != nil {
|
||||||
dbp.ClearSteppingBreakpoints()
|
grp.Selected.ClearSteppingBreakpoints()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbp.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue continues execution of the debugged
|
// Continue continues execution of the debugged
|
||||||
// process. It will continue until it hits a breakpoint
|
// processes. It will continue until it hits a breakpoint
|
||||||
// or is otherwise stopped.
|
// or is otherwise stopped.
|
||||||
func (dbp *Target) Continue() error {
|
func (grp *TargetGroup) Continue() error {
|
||||||
|
if len(grp.targets) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
dbp := grp.Selected
|
||||||
if _, err := dbp.Valid(); err != nil {
|
if _, err := dbp.Valid(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -56,29 +61,29 @@ func (dbp *Target) Continue() error {
|
|||||||
}
|
}
|
||||||
dbp.Breakpoints().WatchOutOfScope = nil
|
dbp.Breakpoints().WatchOutOfScope = nil
|
||||||
dbp.clearHardcodedBreakpoints()
|
dbp.clearHardcodedBreakpoints()
|
||||||
dbp.cctx.CheckAndClearManualStopRequest()
|
grp.cctx.CheckAndClearManualStopRequest()
|
||||||
defer func() {
|
defer func() {
|
||||||
// Make sure we clear internal breakpoints if we simultaneously receive a
|
// Make sure we clear internal breakpoints if we simultaneously receive a
|
||||||
// manual stop request and hit a breakpoint.
|
// manual stop request and hit a breakpoint.
|
||||||
if dbp.cctx.CheckAndClearManualStopRequest() {
|
if grp.cctx.CheckAndClearManualStopRequest() {
|
||||||
dbp.StopReason = StopManual
|
dbp.StopReason = StopManual
|
||||||
dbp.clearHardcodedBreakpoints()
|
dbp.clearHardcodedBreakpoints()
|
||||||
if dbp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
||||||
dbp.ClearSteppingBreakpoints()
|
dbp.ClearSteppingBreakpoints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
if dbp.cctx.CheckAndClearManualStopRequest() {
|
if grp.cctx.CheckAndClearManualStopRequest() {
|
||||||
dbp.StopReason = StopManual
|
dbp.StopReason = StopManual
|
||||||
dbp.clearHardcodedBreakpoints()
|
dbp.clearHardcodedBreakpoints()
|
||||||
if dbp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
||||||
dbp.ClearSteppingBreakpoints()
|
dbp.ClearSteppingBreakpoints()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dbp.ClearCaches()
|
dbp.ClearCaches()
|
||||||
trapthread, stopReason, contOnceErr := dbp.proc.ContinueOnce(dbp.cctx)
|
trapthread, stopReason, contOnceErr := grp.continueOnce([]ProcessInternal{grp.targets[0].proc}, grp.cctx)
|
||||||
dbp.StopReason = stopReason
|
dbp.StopReason = stopReason
|
||||||
|
|
||||||
threads := dbp.ThreadList()
|
threads := dbp.ThreadList()
|
||||||
@ -136,7 +141,7 @@ func (dbp *Target) Continue() error {
|
|||||||
if err := conditionErrors(threads); err != nil {
|
if err := conditionErrors(threads); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if dbp.GetDirection() == Forward {
|
if grp.GetDirection() == Forward {
|
||||||
text, err := disassembleCurrentInstruction(dbp, curthread, 0)
|
text, err := disassembleCurrentInstruction(dbp, curthread, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -155,7 +160,7 @@ func (dbp *Target) Continue() error {
|
|||||||
if err := dbp.ClearSteppingBreakpoints(); err != nil {
|
if err := dbp.ClearSteppingBreakpoints(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return dbp.StepInstruction()
|
return grp.StepInstruction()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
|
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
|
||||||
@ -171,7 +176,7 @@ func (dbp *Target) Continue() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if onNextGoroutine &&
|
if onNextGoroutine &&
|
||||||
(!isTraceOrTraceReturn(curbp.Breakpoint) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
|
(!isTraceOrTraceReturn(curbp.Breakpoint) || grp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
|
||||||
err := dbp.ClearSteppingBreakpoints()
|
err := dbp.ClearSteppingBreakpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -287,27 +292,27 @@ func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step will continue until another source line is reached.
|
// Step resumes the processes in the group, continuing the selected target
|
||||||
// Will step into functions.
|
// until the next source line. Will step into functions.
|
||||||
func (dbp *Target) Step() (err error) {
|
func (grp *TargetGroup) Step() (err error) {
|
||||||
if _, err := dbp.Valid(); err != nil {
|
if _, err := grp.Valid(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if dbp.Breakpoints().HasSteppingBreakpoints() {
|
if grp.HasSteppingBreakpoints() {
|
||||||
return fmt.Errorf("next while nexting")
|
return fmt.Errorf("next while nexting")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = next(dbp, true, false); err != nil {
|
if err = next(grp.Selected, true, false); err != nil {
|
||||||
_ = dbp.ClearSteppingBreakpoints()
|
_ = grp.Selected.ClearSteppingBreakpoints()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bpstate := dbp.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && dbp.GetDirection() == Backward {
|
if bpstate := grp.Selected.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && grp.GetDirection() == Backward {
|
||||||
dbp.ClearSteppingBreakpoints()
|
grp.Selected.ClearSteppingBreakpoints()
|
||||||
return dbp.StepInstruction()
|
return grp.StepInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbp.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// sameGoroutineCondition returns an expression that evaluates to true when
|
// sameGoroutineCondition returns an expression that evaluates to true when
|
||||||
@ -323,17 +328,19 @@ func frameoffCondition(frame *Stackframe) ast.Expr {
|
|||||||
return astutil.Eql(astutil.PkgVar("runtime", "frameoff"), astutil.Int(frame.FrameOffset()))
|
return astutil.Eql(astutil.PkgVar("runtime", "frameoff"), astutil.Int(frame.FrameOffset()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StepOut will continue until the current goroutine exits the
|
// StepOut resumes the processes in the group, continuing the selected target
|
||||||
// function currently being executed or a deferred function is executed
|
// until until the current goroutine exits the function currently being
|
||||||
func (dbp *Target) StepOut() error {
|
// executed or a deferred function is executed
|
||||||
backward := dbp.GetDirection() == Backward
|
func (grp *TargetGroup) StepOut() error {
|
||||||
if _, err := dbp.Valid(); err != nil {
|
backward := grp.GetDirection() == Backward
|
||||||
|
if _, err := grp.Valid(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if dbp.Breakpoints().HasSteppingBreakpoints() {
|
if grp.HasSteppingBreakpoints() {
|
||||||
return fmt.Errorf("next while nexting")
|
return fmt.Errorf("next while nexting")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbp := grp.Selected
|
||||||
selg := dbp.SelectedGoroutine()
|
selg := dbp.SelectedGoroutine()
|
||||||
curthread := dbp.CurrentThread()
|
curthread := dbp.CurrentThread()
|
||||||
|
|
||||||
@ -355,7 +362,7 @@ func (dbp *Target) StepOut() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
return dbp.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
sameGCond := sameGoroutineCondition(selg)
|
sameGCond := sameGoroutineCondition(selg)
|
||||||
@ -366,7 +373,7 @@ func (dbp *Target) StepOut() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
return dbp.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false)
|
deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false)
|
||||||
@ -395,14 +402,15 @@ func (dbp *Target) StepOut() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
return dbp.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StepInstruction will continue the current thread for exactly
|
// StepInstruction will continue the current thread for exactly
|
||||||
// one instruction. This method affects only the thread
|
// one instruction. This method affects only the thread
|
||||||
// associated with the selected goroutine. All other
|
// associated with the selected goroutine. All other
|
||||||
// threads will remain stopped.
|
// threads will remain stopped.
|
||||||
func (dbp *Target) StepInstruction() (err error) {
|
func (grp *TargetGroup) StepInstruction() (err error) {
|
||||||
|
dbp := grp.Selected
|
||||||
thread := dbp.CurrentThread()
|
thread := dbp.CurrentThread()
|
||||||
g := dbp.SelectedGoroutine()
|
g := dbp.SelectedGoroutine()
|
||||||
if g != nil {
|
if g != nil {
|
||||||
@ -412,7 +420,7 @@ func (dbp *Target) StepInstruction() (err error) {
|
|||||||
sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return dbp.Continue()
|
return grp.Continue()
|
||||||
}
|
}
|
||||||
thread = g.Thread
|
thread = g.Thread
|
||||||
}
|
}
|
||||||
@ -464,7 +472,7 @@ func (dbp *Target) StepInstruction() (err error) {
|
|||||||
// when removing instructions belonging to inlined calls we also remove all
|
// when removing instructions belonging to inlined calls we also remove all
|
||||||
// instructions belonging to the current inlined call.
|
// instructions belonging to the current inlined call.
|
||||||
func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
||||||
backward := dbp.GetDirection() == Backward
|
backward := dbp.recman.GetDirection() == Backward
|
||||||
selg := dbp.SelectedGoroutine()
|
selg := dbp.SelectedGoroutine()
|
||||||
curthread := dbp.CurrentThread()
|
curthread := dbp.CurrentThread()
|
||||||
topframe, retframe, err := topframe(selg, curthread)
|
topframe, retframe, err := topframe(selg, curthread)
|
||||||
@ -1086,7 +1094,7 @@ func (tgt *Target) clearHardcodedBreakpoints() {
|
|||||||
func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error {
|
func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error {
|
||||||
mem := tgt.Memory()
|
mem := tgt.Memory()
|
||||||
arch := tgt.BinInfo().Arch
|
arch := tgt.BinInfo().Arch
|
||||||
recorded, _ := tgt.Recorded()
|
recorded, _ := tgt.recman.Recorded()
|
||||||
|
|
||||||
isHardcodedBreakpoint := func(thread Thread, pc uint64) uint64 {
|
isHardcodedBreakpoint := func(thread Thread, pc uint64) uint64 {
|
||||||
for _, bpinstr := range [][]byte{arch.BreakpointInstruction(), arch.AltBreakpointInstruction()} {
|
for _, bpinstr := range [][]byte{arch.BreakpointInstruction(), arch.AltBreakpointInstruction()} {
|
||||||
@ -1150,7 +1158,7 @@ func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Threa
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case loc.Fn.Name == "runtime.breakpoint":
|
case loc.Fn.Name == "runtime.breakpoint":
|
||||||
if recorded, _ := tgt.Recorded(); recorded {
|
if recorded, _ := tgt.recman.Recorded(); recorded {
|
||||||
setHardcodedBreakpoint(thread, loc)
|
setHardcodedBreakpoint(thread, loc)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
123
pkg/proc/target_group.go
Normal file
123
pkg/proc/target_group.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TargetGroup reperesents a group of target processes being debugged that
|
||||||
|
// will be resumed and stopped simultaneously.
|
||||||
|
// New targets are automatically added to the group if exec catching is
|
||||||
|
// enabled and the backend supports it, otherwise the group will always
|
||||||
|
// contain a single target process.
|
||||||
|
type TargetGroup struct {
|
||||||
|
targets []*Target
|
||||||
|
Selected *Target
|
||||||
|
|
||||||
|
RecordingManipulation
|
||||||
|
recman RecordingManipulationInternal
|
||||||
|
|
||||||
|
// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
|
||||||
|
// will keep the stepping breakpoints instead of clearing them.
|
||||||
|
KeepSteppingBreakpoints KeepSteppingBreakpoints
|
||||||
|
|
||||||
|
LogicalBreakpoints map[int]*LogicalBreakpoint
|
||||||
|
|
||||||
|
continueOnce ContinueOnceFunc
|
||||||
|
cctx *ContinueOnceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGroup creates a TargetGroup containing the specified Target.
|
||||||
|
func NewGroup(t *Target) *TargetGroup {
|
||||||
|
if t.partOfGroup {
|
||||||
|
panic("internal error: target is already part of a group")
|
||||||
|
}
|
||||||
|
t.partOfGroup = true
|
||||||
|
return &TargetGroup{
|
||||||
|
RecordingManipulation: t.recman,
|
||||||
|
targets: []*Target{t},
|
||||||
|
Selected: t,
|
||||||
|
cctx: &ContinueOnceContext{},
|
||||||
|
recman: t.recman,
|
||||||
|
LogicalBreakpoints: t.Breakpoints().Logical,
|
||||||
|
continueOnce: t.continueOnce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Targets returns a slice of targets in the group.
|
||||||
|
func (grp *TargetGroup) Targets() []*Target {
|
||||||
|
return grp.targets
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if any target in the target group is valid.
|
||||||
|
func (grp *TargetGroup) Valid() (bool, error) {
|
||||||
|
var err0 error
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
ok, err := t.Valid()
|
||||||
|
if ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
err0 = err
|
||||||
|
}
|
||||||
|
return false, err0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detach detaches all targets in the group.
|
||||||
|
func (grp *TargetGroup) Detach(kill bool) error {
|
||||||
|
var errs []string
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
isvalid, _ := t.Valid()
|
||||||
|
if !isvalid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := t.detach(kill)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Sprintf("could not detach process %d: %v", t.Pid(), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSteppingBreakpoints returns true if any of the targets has stepping breakpoints set.
|
||||||
|
func (grp *TargetGroup) HasSteppingBreakpoints() bool {
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
if t.Breakpoints().HasSteppingBreakpoints() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSteppingBreakpoints removes all stepping breakpoints.
|
||||||
|
func (grp *TargetGroup) ClearSteppingBreakpoints() error {
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
if t.Breakpoints().HasSteppingBreakpoints() {
|
||||||
|
return t.ClearSteppingBreakpoints()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThreadList returns a list of all threads in all target processes.
|
||||||
|
func (grp *TargetGroup) ThreadList() []Thread {
|
||||||
|
r := []Thread{}
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
r = append(r, t.ThreadList()...)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetForThread returns the target containing the given thread.
|
||||||
|
func (grp *TargetGroup) TargetForThread(thread Thread) *Target {
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
for _, th := range t.ThreadList() {
|
||||||
|
if th == thread {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1232,44 +1232,45 @@ func TestCallFunction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
|
||||||
|
grp := proc.NewGroup(p)
|
||||||
testCallFunctionSetBreakpoint(t, p, fixture)
|
testCallFunctionSetBreakpoint(t, p, fixture)
|
||||||
|
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
testCallFunction(t, p, tc)
|
testCallFunction(t, grp, p, tc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) {
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) {
|
||||||
for _, tc := range testcases112 {
|
for _, tc := range testcases112 {
|
||||||
testCallFunction(t, p, tc)
|
testCallFunction(t, grp, p, tc)
|
||||||
}
|
}
|
||||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
||||||
for _, tc := range testcasesBefore114After112 {
|
for _, tc := range testcasesBefore114After112 {
|
||||||
testCallFunction(t, p, tc)
|
testCallFunction(t, grp, p, tc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
|
||||||
for _, tc := range testcases113 {
|
for _, tc := range testcases113 {
|
||||||
testCallFunction(t, p, tc)
|
testCallFunction(t, grp, p, tc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
||||||
for _, tc := range testcases114 {
|
for _, tc := range testcases114 {
|
||||||
testCallFunction(t, p, tc)
|
testCallFunction(t, grp, p, tc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
|
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
|
||||||
for _, tc := range testcases117 {
|
for _, tc := range testcases117 {
|
||||||
testCallFunction(t, p, tc)
|
testCallFunction(t, grp, p, tc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
|
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
|
||||||
testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
|
testCallFunction(t, grp, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1284,7 +1285,7 @@ func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, fixture protest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
|
func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc testCaseCallFunction) {
|
||||||
const unsafePrefix = "-unsafe "
|
const unsafePrefix = "-unsafe "
|
||||||
|
|
||||||
var callExpr, varExpr string
|
var callExpr, varExpr string
|
||||||
@ -1302,7 +1303,7 @@ func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
|
|||||||
checkEscape = false
|
checkEscape = false
|
||||||
}
|
}
|
||||||
t.Logf("call %q", tc.expr)
|
t.Logf("call %q", tc.expr)
|
||||||
err := proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), callExpr, pnormalLoadConfig, checkEscape)
|
err := proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), callExpr, pnormalLoadConfig, checkEscape)
|
||||||
if tc.err != nil {
|
if tc.err != nil {
|
||||||
t.Logf("\terr = %v\n", err)
|
t.Logf("\terr = %v\n", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@ -1558,7 +1558,7 @@ func (s *Session) onConfigurationDoneRequest(request *dap.ConfigurationDoneReque
|
|||||||
}
|
}
|
||||||
s.send(e)
|
s.send(e)
|
||||||
}
|
}
|
||||||
s.debugger.Target().KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints | proc.TracepointKeepsSteppingBreakpoints
|
s.debugger.TargetGroup().KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints | proc.TracepointKeepsSteppingBreakpoints
|
||||||
|
|
||||||
s.logToConsole("Type 'dlv help' for list of commands.")
|
s.logToConsole("Type 'dlv help' for list of commands.")
|
||||||
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
|
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
|
||||||
|
|||||||
@ -61,7 +61,7 @@ type Debugger struct {
|
|||||||
processArgs []string
|
processArgs []string
|
||||||
|
|
||||||
targetMutex sync.Mutex
|
targetMutex sync.Mutex
|
||||||
target *proc.Target
|
target *proc.TargetGroup
|
||||||
|
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
err = noDebugErrorWarning(err)
|
err = noDebugErrorWarning(err)
|
||||||
return nil, attachErrorMessage(d.config.AttachPid, err)
|
return nil, attachErrorMessage(d.config.AttachPid, err)
|
||||||
}
|
}
|
||||||
d.target = p
|
d.target = proc.NewGroup(p)
|
||||||
|
|
||||||
case d.config.CoreFile != "":
|
case d.config.CoreFile != "":
|
||||||
var p *proc.Target
|
var p *proc.Target
|
||||||
@ -178,7 +178,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
err = go11DecodeErrorCheck(err)
|
err = go11DecodeErrorCheck(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
d.target = p
|
d.target = proc.NewGroup(p)
|
||||||
if err := d.checkGoVersion(); err != nil {
|
if err := d.checkGoVersion(); err != nil {
|
||||||
d.target.Detach(true)
|
d.target.Detach(true)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -197,7 +197,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
}
|
}
|
||||||
if p != nil {
|
if p != nil {
|
||||||
// if p == nil and err == nil then we are doing a recording, don't touch d.target
|
// if p == nil and err == nil then we are doing a recording, don't touch d.target
|
||||||
d.target = p
|
d.target = proc.NewGroup(p)
|
||||||
}
|
}
|
||||||
if err := d.checkGoVersion(); err != nil {
|
if err := d.checkGoVersion(); err != nil {
|
||||||
d.target.Detach(true)
|
d.target.Detach(true)
|
||||||
@ -225,7 +225,7 @@ func (d *Debugger) checkGoVersion() error {
|
|||||||
// do not do anything if we are still recording
|
// do not do anything if we are still recording
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
producer := d.target.BinInfo().Producer()
|
producer := d.target.Selected.BinInfo().Producer()
|
||||||
if producer == "" {
|
if producer == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ func (d *Debugger) checkGoVersion() error {
|
|||||||
func (d *Debugger) TargetGoVersion() string {
|
func (d *Debugger) TargetGoVersion() string {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.target.BinInfo().Producer()
|
return d.target.Selected.BinInfo().Producer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch will start a process with the given args and working directory.
|
// Launch will start a process with the given args and working directory.
|
||||||
@ -285,7 +285,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
d.recordingDone()
|
d.recordingDone()
|
||||||
d.target = p
|
d.target = proc.NewGroup(p)
|
||||||
if err := d.checkGoVersion(); err != nil {
|
if err := d.checkGoVersion(); err != nil {
|
||||||
d.log.Error(err)
|
d.log.Error(err)
|
||||||
err := d.target.Detach(true)
|
err := d.target.Detach(true)
|
||||||
@ -368,7 +368,7 @@ func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error)
|
|||||||
func (d *Debugger) ProcessPid() int {
|
func (d *Debugger) ProcessPid() int {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.target.Pid()
|
return d.target.Selected.Pid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastModified returns the time that the process' executable was last
|
// LastModified returns the time that the process' executable was last
|
||||||
@ -376,7 +376,7 @@ func (d *Debugger) ProcessPid() int {
|
|||||||
func (d *Debugger) LastModified() time.Time {
|
func (d *Debugger) LastModified() time.Time {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.target.BinInfo().LastModified()
|
return d.target.Selected.BinInfo().LastModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionReturnLocations returns all return locations
|
// FunctionReturnLocations returns all return locations
|
||||||
@ -387,7 +387,7 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
|
|||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
p = d.target
|
p = d.target.Selected
|
||||||
g = p.SelectedGoroutine()
|
g = p.SelectedGoroutine()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -463,12 +463,6 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
return nil, ErrCanNotRestart
|
return nil, ErrCanNotRestart
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid, _ := d.target.Valid(); valid && !recorded {
|
|
||||||
// Ensure the process is in a PTRACE_STOP.
|
|
||||||
if err := stopProcess(d.target.Pid()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := d.detach(true); err != nil {
|
if err := d.detach(true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -514,9 +508,9 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
}
|
}
|
||||||
|
|
||||||
discarded := []api.DiscardedBreakpoint{}
|
discarded := []api.DiscardedBreakpoint{}
|
||||||
p.Breakpoints().Logical = d.target.Breakpoints().Logical
|
p.Breakpoints().Logical = d.target.LogicalBreakpoints
|
||||||
d.target = p
|
d.target = proc.NewGroup(p)
|
||||||
for _, oldBp := range d.target.Breakpoints().Logical {
|
for _, oldBp := range d.target.LogicalBreakpoints {
|
||||||
if oldBp.LogicalID < 0 || !oldBp.Enabled {
|
if oldBp.LogicalID < 0 || !oldBp.Enabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -566,16 +560,19 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
|
|||||||
goroutine *api.Goroutine
|
goroutine *api.Goroutine
|
||||||
)
|
)
|
||||||
|
|
||||||
if d.target.SelectedGoroutine() != nil {
|
tgt := d.target.Selected
|
||||||
goroutine = api.ConvertGoroutine(d.target, d.target.SelectedGoroutine())
|
|
||||||
|
if tgt.SelectedGoroutine() != nil {
|
||||||
|
goroutine = api.ConvertGoroutine(tgt, tgt.SelectedGoroutine())
|
||||||
}
|
}
|
||||||
|
|
||||||
exited := false
|
exited := false
|
||||||
if _, err := d.target.Valid(); err != nil {
|
if _, err := tgt.Valid(); err != nil {
|
||||||
_, exited = err.(proc.ErrProcessExited)
|
_, exited = err.(proc.ErrProcessExited)
|
||||||
}
|
}
|
||||||
|
|
||||||
state = &api.DebuggerState{
|
state = &api.DebuggerState{
|
||||||
|
Pid: tgt.Pid(),
|
||||||
SelectedGoroutine: goroutine,
|
SelectedGoroutine: goroutine,
|
||||||
Exited: exited,
|
Exited: exited,
|
||||||
}
|
}
|
||||||
@ -589,22 +586,23 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.Threads = append(state.Threads, th)
|
state.Threads = append(state.Threads, th)
|
||||||
if thread.ThreadID() == d.target.CurrentThread().ThreadID() {
|
if thread.ThreadID() == tgt.CurrentThread().ThreadID() {
|
||||||
state.CurrentThread = th
|
state.CurrentThread = th
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.NextInProgress = d.target.Breakpoints().HasSteppingBreakpoints()
|
state.NextInProgress = d.target.HasSteppingBreakpoints()
|
||||||
|
|
||||||
if recorded, _ := d.target.Recorded(); recorded {
|
if recorded, _ := d.target.Recorded(); recorded {
|
||||||
state.When, _ = d.target.When()
|
state.When, _ = d.target.When()
|
||||||
}
|
}
|
||||||
|
|
||||||
state.WatchOutOfScope = make([]*api.Breakpoint, 0, len(d.target.Breakpoints().WatchOutOfScope))
|
for _, t := range d.target.Targets() {
|
||||||
for _, bp := range d.target.Breakpoints().WatchOutOfScope {
|
for _, bp := range t.Breakpoints().WatchOutOfScope {
|
||||||
abp := api.ConvertLogicalBreakpoint(bp.Logical)
|
abp := api.ConvertLogicalBreakpoint(bp.Logical)
|
||||||
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
|
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
|
||||||
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
|
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
@ -641,6 +639,15 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
//TODO(aarzilli):
|
||||||
|
// - the calls to FindFileLocation and FindFunctionLocation need to be done on all targets
|
||||||
|
// - the Addrs slice and the Addr field need to be converted to a format
|
||||||
|
// that can specify to which target an address belongs when there are
|
||||||
|
// multiple targets (but this must happen in a backwards compatible way)
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addrs []uint64
|
addrs []uint64
|
||||||
err error
|
err error
|
||||||
@ -660,16 +667,19 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// Accept fileName which is case-insensitive and slash-insensitive match
|
// Accept fileName which is case-insensitive and slash-insensitive match
|
||||||
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
|
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
|
||||||
for _, symFile := range d.target.BinInfo().Sources {
|
caseInsensitiveSearch:
|
||||||
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
|
for _, t := range d.target.Targets() {
|
||||||
fileName = symFile
|
for _, symFile := range t.BinInfo().Sources {
|
||||||
break
|
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
|
||||||
|
fileName = symFile
|
||||||
|
break caseInsensitiveSearch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addrs, err = proc.FindFileLocation(d.target, fileName, requestedBp.Line)
|
addrs, err = proc.FindFileLocation(d.target.Selected, fileName, requestedBp.Line)
|
||||||
case len(requestedBp.FunctionName) > 0:
|
case len(requestedBp.FunctionName) > 0:
|
||||||
addrs, err = proc.FindFunctionLocation(d.target, requestedBp.FunctionName, requestedBp.Line)
|
addrs, err = proc.FindFunctionLocation(d.target.Selected, requestedBp.FunctionName, requestedBp.Line)
|
||||||
case len(requestedBp.Addrs) > 0:
|
case len(requestedBp.Addrs) > 0:
|
||||||
addrs = requestedBp.Addrs
|
addrs = requestedBp.Addrs
|
||||||
default:
|
default:
|
||||||
@ -704,7 +714,10 @@ func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint {
|
|||||||
// createLogicalBreakpoint creates one physical breakpoint for each address
|
// createLogicalBreakpoint creates one physical breakpoint for each address
|
||||||
// in addrs and associates all of them with the same logical breakpoint.
|
// in addrs and associates all of them with the same logical breakpoint.
|
||||||
func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
|
func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
|
||||||
p := d.target
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
|
||||||
if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
|
if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
|
||||||
abp := d.convertBreakpoint(lbp)
|
abp := d.convertBreakpoint(lbp)
|
||||||
@ -752,13 +765,17 @@ func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Break
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error {
|
func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error {
|
||||||
addrs, err := proc.FindFileLocation(d.target, lbp.File, lbp.Line)
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
addrs, err := proc.FindFileLocation(p, lbp.File, lbp.Line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bps := make([]*proc.Breakpoint, len(addrs))
|
bps := make([]*proc.Breakpoint, len(addrs))
|
||||||
for i := range addrs {
|
for i := range addrs {
|
||||||
bps[i], err = d.target.SetBreakpoint(lbp.LogicalID, addrs[i], proc.UserBreakpoint, nil)
|
bps[i], err = p.SetBreakpoint(lbp.LogicalID, addrs[i], proc.UserBreakpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -771,7 +788,7 @@ func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error
|
|||||||
if bp == nil {
|
if bp == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err1 := d.target.ClearBreakpoint(bp.Addr); err1 != nil {
|
if err1 := p.ClearBreakpoint(bp.Addr); err1 != nil {
|
||||||
return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
|
return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -781,12 +798,16 @@ func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) clearPhysicalBreakpoints(id int) error {
|
func (d *Debugger) clearPhysicalBreakpoints(id int) error {
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
var errs []error
|
var errs []error
|
||||||
n := 0
|
n := 0
|
||||||
for _, bp := range d.target.Breakpoints().M {
|
for _, bp := range p.Breakpoints().M {
|
||||||
if bp.LogicalID() == id {
|
if bp.LogicalID() == id {
|
||||||
n++
|
n++
|
||||||
err := d.target.ClearBreakpoint(bp.Addr)
|
err := p.ClearBreakpoint(bp.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
@ -817,15 +838,21 @@ func isBreakpointExistsErr(err error) bool {
|
|||||||
func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
|
func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
return d.target.SetEBPFTracepoint(fnName)
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
return p.SetEBPFTracepoint(fnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// amendBreakpoint will update the breakpoint with the matching ID.
|
// amendBreakpoint will update the breakpoint with the matching ID.
|
||||||
// It also enables or disables the breakpoint.
|
// It also enables or disables the breakpoint.
|
||||||
// We can consume this function to avoid locking a goroutine.
|
// We can consume this function to avoid locking a goroutine.
|
||||||
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error {
|
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error {
|
||||||
original := d.target.Breakpoints().Logical[amend.ID]
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
original := d.target.LogicalBreakpoints[amend.ID]
|
||||||
if original == nil {
|
if original == nil {
|
||||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||||
}
|
}
|
||||||
@ -956,15 +983,20 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
|
|||||||
|
|
||||||
// clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
|
// clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
|
||||||
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
|
||||||
if requestedBp.ID <= 0 {
|
if requestedBp.ID <= 0 {
|
||||||
bp := d.target.Breakpoints().M[requestedBp.Addr]
|
bp := p.Breakpoints().M[requestedBp.Addr]
|
||||||
requestedBp.ID = bp.LogicalID()
|
requestedBp.ID = bp.LogicalID()
|
||||||
}
|
}
|
||||||
|
|
||||||
lbp := d.target.Breakpoints().Logical[requestedBp.ID]
|
lbp := d.target.LogicalBreakpoints[requestedBp.ID]
|
||||||
clearedBp := d.convertBreakpoint(lbp)
|
clearedBp := d.convertBreakpoint(lbp)
|
||||||
|
|
||||||
delete(d.target.Breakpoints().Logical, requestedBp.ID)
|
delete(d.target.LogicalBreakpoints, requestedBp.ID)
|
||||||
|
|
||||||
err := d.clearPhysicalBreakpoints(requestedBp.ID)
|
err := d.clearPhysicalBreakpoints(requestedBp.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1006,10 +1038,14 @@ func isBpHitCondNotSatisfiable(bp *api.Breakpoint) bool {
|
|||||||
func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
|
func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
|
||||||
abps := []*api.Breakpoint{}
|
abps := []*api.Breakpoint{}
|
||||||
if all {
|
if all {
|
||||||
for _, bp := range d.target.Breakpoints().M {
|
for _, bp := range p.Breakpoints().M {
|
||||||
var abp *api.Breakpoint
|
var abp *api.Breakpoint
|
||||||
if bp.Logical != nil {
|
if bp.Logical != nil {
|
||||||
abp = api.ConvertLogicalBreakpoint(bp.Logical)
|
abp = api.ConvertLogicalBreakpoint(bp.Logical)
|
||||||
@ -1021,7 +1057,7 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
|
|||||||
abps = append(abps, abp)
|
abps = append(abps, abp)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, lbp := range d.target.Breakpoints().Logical {
|
for _, lbp := range d.target.LogicalBreakpoints {
|
||||||
abps = append(abps, d.convertBreakpoint(lbp))
|
abps = append(abps, d.convertBreakpoint(lbp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1032,7 +1068,7 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
|
|||||||
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
lbp := d.target.Breakpoints().Logical[id]
|
lbp := d.target.LogicalBreakpoints[id]
|
||||||
if lbp == nil {
|
if lbp == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1040,8 +1076,13 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
|
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
|
||||||
var bps []*proc.Breakpoint
|
var bps []*proc.Breakpoint
|
||||||
for _, bp := range d.target.Breakpoints().M {
|
for _, bp := range p.Breakpoints().M {
|
||||||
if bp.LogicalID() == id {
|
if bp.LogicalID() == id {
|
||||||
bps = append(bps, bp)
|
bps = append(bps, bp)
|
||||||
}
|
}
|
||||||
@ -1057,7 +1098,7 @@ func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
||||||
for _, lbp := range d.target.Breakpoints().Logical {
|
for _, lbp := range d.target.LogicalBreakpoints {
|
||||||
if lbp.Name == name {
|
if lbp.Name == name {
|
||||||
return d.convertBreakpoint(lbp)
|
return d.convertBreakpoint(lbp)
|
||||||
}
|
}
|
||||||
@ -1067,12 +1108,17 @@ func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
|||||||
|
|
||||||
// CreateWatchpoint creates a watchpoint on the specified expression.
|
// CreateWatchpoint creates a watchpoint on the specified expression.
|
||||||
func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
|
func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
if len(d.target.Targets()) != 1 {
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
p := d.target.Selected
|
||||||
|
|
||||||
|
s, err := proc.ConvertEvalScope(p, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
d.breakpointIDCounter++
|
d.breakpointIDCounter++
|
||||||
bp, err := d.target.SetWatchpoint(d.breakpointIDCounter, s, expr, proc.WatchType(wtype), nil)
|
bp, err := p.SetWatchpoint(d.breakpointIDCounter, s, expr, proc.WatchType(wtype), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1116,7 +1162,7 @@ func (d *Debugger) FindGoroutine(id int) (*proc.G, error) {
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
return proc.FindGoroutine(d.target, id)
|
return proc.FindGoroutine(d.target.Selected, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) setRunning(running bool) {
|
func (d *Debugger) setRunning(running bool) {
|
||||||
@ -1184,9 +1230,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
|
|||||||
if command.ReturnInfoLoadConfig == nil {
|
if command.ReturnInfoLoadConfig == nil {
|
||||||
return nil, errors.New("can not call function with nil ReturnInfoLoadConfig")
|
return nil, errors.New("can not call function with nil ReturnInfoLoadConfig")
|
||||||
}
|
}
|
||||||
g := d.target.SelectedGoroutine()
|
g := d.target.Selected.SelectedGoroutine()
|
||||||
if command.GoroutineID > 0 {
|
if command.GoroutineID > 0 {
|
||||||
g, err = proc.FindGoroutine(d.target, command.GoroutineID)
|
g, err = proc.FindGoroutine(d.target.Selected, command.GoroutineID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1248,14 +1294,14 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
|
|||||||
err = d.target.StepOut()
|
err = d.target.StepOut()
|
||||||
case api.SwitchThread:
|
case api.SwitchThread:
|
||||||
d.log.Debugf("switching to thread %d", command.ThreadID)
|
d.log.Debugf("switching to thread %d", command.ThreadID)
|
||||||
err = d.target.SwitchThread(command.ThreadID)
|
err = d.target.Selected.SwitchThread(command.ThreadID)
|
||||||
withBreakpointInfo = false
|
withBreakpointInfo = false
|
||||||
case api.SwitchGoroutine:
|
case api.SwitchGoroutine:
|
||||||
d.log.Debugf("switching to goroutine %d", command.GoroutineID)
|
d.log.Debugf("switching to goroutine %d", command.GoroutineID)
|
||||||
var g *proc.G
|
var g *proc.G
|
||||||
g, err = proc.FindGoroutine(d.target, command.GoroutineID)
|
g, err = proc.FindGoroutine(d.target.Selected, command.GoroutineID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = d.target.SwitchGoroutine(g)
|
err = d.target.Selected.SwitchGoroutine(g)
|
||||||
}
|
}
|
||||||
withBreakpointInfo = false
|
withBreakpointInfo = false
|
||||||
case api.Halt:
|
case api.Halt:
|
||||||
@ -1266,7 +1312,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread {
|
if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread {
|
||||||
state := &api.DebuggerState{}
|
state := &api.DebuggerState{}
|
||||||
state.Pid = d.target.Pid()
|
state.Pid = d.target.Selected.Pid()
|
||||||
state.Exited = true
|
state.Exited = true
|
||||||
state.ExitStatus = pe.Status
|
state.ExitStatus = pe.Status
|
||||||
state.Err = pe
|
state.Err = pe
|
||||||
@ -1298,6 +1344,8 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error {
|
func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error {
|
||||||
|
//TODO(aarzilli): this doesn't work when there are multiple targets because the state.Threads slice will contain threads from all targets, not just d.target .Selected
|
||||||
|
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1312,15 +1360,15 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
|
|||||||
state.Threads[i].BreakpointInfo = bpi
|
state.Threads[i].BreakpointInfo = bpi
|
||||||
|
|
||||||
if bp.Goroutine {
|
if bp.Goroutine {
|
||||||
g, err := proc.GetG(d.target.CurrentThread())
|
g, err := proc.GetG(d.target.Selected.CurrentThread())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bpi.Goroutine = api.ConvertGoroutine(d.target, g)
|
bpi.Goroutine = api.ConvertGoroutine(d.target.Selected, g)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bp.Stacktrace > 0 {
|
if bp.Stacktrace > 0 {
|
||||||
rawlocs, err := proc.ThreadStacktrace(d.target.CurrentThread(), bp.Stacktrace)
|
rawlocs, err := proc.ThreadStacktrace(d.target.Selected.CurrentThread(), bp.Stacktrace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1330,7 +1378,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread, found := d.target.FindThread(state.Threads[i].ID)
|
thread, found := d.target.Selected.FindThread(state.Threads[i].ID)
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("could not find thread %d", state.Threads[i].ID)
|
return fmt.Errorf("could not find thread %d", state.Threads[i].ID)
|
||||||
}
|
}
|
||||||
@ -1340,7 +1388,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := proc.GoroutineScope(d.target, thread)
|
s, err := proc.GoroutineScope(d.target.Selected, thread)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1382,14 +1430,33 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
files := []string{}
|
files := []string{}
|
||||||
for _, f := range d.target.BinInfo().Sources {
|
for _, t := range d.target.Targets() {
|
||||||
if regex.Match([]byte(f)) {
|
for _, f := range t.BinInfo().Sources {
|
||||||
files = append(files, f)
|
if regex.Match([]byte(f)) {
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort.Strings(files)
|
||||||
|
files = uniq(files)
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uniq(s []string) []string {
|
||||||
|
if len(s) <= 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
src, dst := 1, 1
|
||||||
|
for src < len(s) {
|
||||||
|
if s[src] != s[dst-1] {
|
||||||
|
s[dst] = s[src]
|
||||||
|
dst++
|
||||||
|
}
|
||||||
|
src++
|
||||||
|
}
|
||||||
|
return s[:dst]
|
||||||
|
}
|
||||||
|
|
||||||
// Functions returns a list of functions in the target process.
|
// Functions returns a list of functions in the target process.
|
||||||
func (d *Debugger) Functions(filter string) ([]string, error) {
|
func (d *Debugger) Functions(filter string) ([]string, error) {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
@ -1401,11 +1468,15 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
funcs := []string{}
|
funcs := []string{}
|
||||||
for _, f := range d.target.BinInfo().Functions {
|
for _, t := range d.target.Targets() {
|
||||||
if regex.MatchString(f.Name) {
|
for _, f := range t.BinInfo().Functions {
|
||||||
funcs = append(funcs, f.Name)
|
if regex.MatchString(f.Name) {
|
||||||
|
funcs = append(funcs, f.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort.Strings(funcs)
|
||||||
|
funcs = uniq(funcs)
|
||||||
return funcs, nil
|
return funcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1419,17 +1490,22 @@ func (d *Debugger) Types(filter string) ([]string, error) {
|
|||||||
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
|
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
types, err := d.target.BinInfo().Types()
|
r := []string{}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := make([]string, 0, len(types))
|
for _, t := range d.target.Targets() {
|
||||||
for _, typ := range types {
|
types, err := t.BinInfo().Types()
|
||||||
if regex.Match([]byte(typ)) {
|
if err != nil {
|
||||||
r = append(r, typ)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, typ := range types {
|
||||||
|
if regex.Match([]byte(typ)) {
|
||||||
|
r = append(r, typ)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort.Strings(r)
|
||||||
|
r = uniq(r)
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
@ -1439,13 +1515,14 @@ func (d *Debugger) Types(filter string) ([]string, error) {
|
|||||||
func (d *Debugger) PackageVariables(filter string, cfg proc.LoadConfig) ([]*proc.Variable, error) {
|
func (d *Debugger) PackageVariables(filter string, cfg proc.LoadConfig) ([]*proc.Variable, error) {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
p := d.target.Selected
|
||||||
|
|
||||||
regex, err := regexp.Compile(filter)
|
regex, err := regexp.Compile(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
|
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, err := proc.ThreadScope(d.target, d.target.CurrentThread())
|
scope, err := proc.ThreadScope(p, p.CurrentThread())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1467,7 +1544,7 @@ func (d *Debugger) ThreadRegisters(threadID int, floatingPoint bool) (*op.DwarfR
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
thread, found := d.target.FindThread(threadID)
|
thread, found := d.target.Selected.FindThread(threadID)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||||
}
|
}
|
||||||
@ -1475,7 +1552,7 @@ func (d *Debugger) ThreadRegisters(threadID int, floatingPoint bool) (*op.DwarfR
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return d.target.BinInfo().Arch.RegistersToDwarfRegisters(0, regs), nil
|
return d.target.Selected.BinInfo().Arch.RegistersToDwarfRegisters(0, regs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScopeRegisters returns registers for the specified scope.
|
// ScopeRegisters returns registers for the specified scope.
|
||||||
@ -1483,7 +1560,7 @@ func (d *Debugger) ScopeRegisters(goid, frame, deferredCall int, floatingPoint b
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1492,7 +1569,7 @@ func (d *Debugger) ScopeRegisters(goid, frame, deferredCall int, floatingPoint b
|
|||||||
|
|
||||||
// DwarfRegisterToString returns the name and value representation of the given register.
|
// DwarfRegisterToString returns the name and value representation of the given register.
|
||||||
func (d *Debugger) DwarfRegisterToString(i int, reg *op.DwarfRegister) (string, bool, string) {
|
func (d *Debugger) DwarfRegisterToString(i int, reg *op.DwarfRegister) (string, bool, string) {
|
||||||
return d.target.BinInfo().Arch.DwarfRegisterToString(i, reg)
|
return d.target.Selected.BinInfo().Arch.DwarfRegisterToString(i, reg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalVariables returns a list of the local variables.
|
// LocalVariables returns a list of the local variables.
|
||||||
@ -1500,7 +1577,7 @@ func (d *Debugger) LocalVariables(goid, frame, deferredCall int, cfg proc.LoadCo
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1512,7 +1589,7 @@ func (d *Debugger) FunctionArguments(goid, frame, deferredCall int, cfg proc.Loa
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1524,7 +1601,7 @@ func (d *Debugger) Function(goid, frame, deferredCall int, cfg proc.LoadConfig)
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1537,7 +1614,7 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, expr strin
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1558,7 +1635,7 @@ func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, val
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1569,7 +1646,7 @@ func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, val
|
|||||||
func (d *Debugger) Goroutines(start, count int) ([]*proc.G, int, error) {
|
func (d *Debugger) Goroutines(start, count int) ([]*proc.G, int, error) {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return proc.GoroutinesInfo(d.target, start, count)
|
return proc.GoroutinesInfo(d.target.Selected, start, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterGoroutines returns the goroutines in gs that satisfy the specified filters.
|
// FilterGoroutines returns the goroutines in gs that satisfy the specified filters.
|
||||||
@ -1583,7 +1660,7 @@ func (d *Debugger) FilterGoroutines(gs []*proc.G, filters []api.ListGoroutinesFi
|
|||||||
for _, g := range gs {
|
for _, g := range gs {
|
||||||
ok := true
|
ok := true
|
||||||
for i := range filters {
|
for i := range filters {
|
||||||
if !matchGoroutineFilter(d.target, g, &filters[i]) {
|
if !matchGoroutineFilter(d.target.Selected, g, &filters[i]) {
|
||||||
ok = false
|
ok = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1663,13 +1740,13 @@ func (d *Debugger) GroupGoroutines(gs []*proc.G, group *api.GoroutineGroupingOpt
|
|||||||
case api.GoroutineGoLoc:
|
case api.GoroutineGoLoc:
|
||||||
key = formatLoc(g.Go())
|
key = formatLoc(g.Go())
|
||||||
case api.GoroutineStartLoc:
|
case api.GoroutineStartLoc:
|
||||||
key = formatLoc(g.StartLoc(d.target))
|
key = formatLoc(g.StartLoc(d.target.Selected))
|
||||||
case api.GoroutineLabel:
|
case api.GoroutineLabel:
|
||||||
key = fmt.Sprintf("%s=%s", group.GroupByKey, g.Labels()[group.GroupByKey])
|
key = fmt.Sprintf("%s=%s", group.GroupByKey, g.Labels()[group.GroupByKey])
|
||||||
case api.GoroutineRunning:
|
case api.GoroutineRunning:
|
||||||
key = fmt.Sprintf("running=%v", g.Thread != nil)
|
key = fmt.Sprintf("running=%v", g.Thread != nil)
|
||||||
case api.GoroutineUser:
|
case api.GoroutineUser:
|
||||||
key = fmt.Sprintf("user=%v", !g.System(d.target))
|
key = fmt.Sprintf("user=%v", !g.System(d.target.Selected))
|
||||||
}
|
}
|
||||||
if len(groupMembers[key]) < group.MaxGroupMembers {
|
if len(groupMembers[key]) < group.MaxGroupMembers {
|
||||||
groupMembers[key] = append(groupMembers[key], g)
|
groupMembers[key] = append(groupMembers[key], g)
|
||||||
@ -1708,13 +1785,13 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, opts api.StacktraceOptions
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := proc.FindGoroutine(d.target, goroutineID)
|
g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if g == nil {
|
if g == nil {
|
||||||
return proc.ThreadStacktrace(d.target.CurrentThread(), depth)
|
return proc.ThreadStacktrace(d.target.Selected.CurrentThread(), depth)
|
||||||
} else {
|
} else {
|
||||||
return g.Stacktrace(depth, proc.StacktraceOptions(opts))
|
return g.Stacktrace(depth, proc.StacktraceOptions(opts))
|
||||||
}
|
}
|
||||||
@ -1729,7 +1806,7 @@ func (d *Debugger) Ancestors(goroutineID, numAncestors, depth int) ([]api.Ancest
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := proc.FindGoroutine(d.target, goroutineID)
|
g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1737,7 +1814,7 @@ func (d *Debugger) Ancestors(goroutineID, numAncestors, depth int) ([]api.Ancest
|
|||||||
return nil, errors.New("no selected goroutine")
|
return nil, errors.New("no selected goroutine")
|
||||||
}
|
}
|
||||||
|
|
||||||
ancestors, err := proc.Ancestors(d.target, g, numAncestors)
|
ancestors, err := proc.Ancestors(d.target.Selected, g, numAncestors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1789,7 +1866,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
|
|||||||
}
|
}
|
||||||
if cfg != nil && rawlocs[i].Current.Fn != nil {
|
if cfg != nil && rawlocs[i].Current.Fn != nil {
|
||||||
var err error
|
var err error
|
||||||
scope := proc.FrameToScope(d.target, d.target.Memory(), nil, rawlocs[i:]...)
|
scope := proc.FrameToScope(d.target.Selected, d.target.Selected.Memory(), nil, rawlocs[i:]...)
|
||||||
locals, err := scope.LocalVariables(*cfg)
|
locals, err := scope.LocalVariables(*cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1811,8 +1888,8 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
|
|||||||
func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
|
func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
|
||||||
r := make([]api.Defer, len(defers))
|
r := make([]api.Defer, len(defers))
|
||||||
for i := range defers {
|
for i := range defers {
|
||||||
ddf, ddl, ddfn := defers[i].DeferredFunc(d.target)
|
ddf, ddl, ddfn := defers[i].DeferredFunc(d.target.Selected)
|
||||||
drf, drl, drfn := d.target.BinInfo().PCToLine(defers[i].DeferPC)
|
drf, drl, drfn := d.target.Selected.BinInfo().PCToLine(defers[i].DeferPC)
|
||||||
|
|
||||||
r[i] = api.Defer{
|
r[i] = api.Defer{
|
||||||
DeferredLoc: api.ConvertLocation(proc.Location{
|
DeferredLoc: api.ConvertLocation(proc.Location{
|
||||||
@ -1848,7 +1925,7 @@ func (d *Debugger) CurrentPackage() (string, error) {
|
|||||||
if _, err := d.target.Valid(); err != nil {
|
if _, err := d.target.Valid(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
loc, err := d.target.CurrentThread().Location()
|
loc, err := d.target.Selected.CurrentThread().Location()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -1863,6 +1940,13 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
//TODO(aarzilli): if there is more than one target process all must be
|
||||||
|
//searched and the addresses returned need to specify which target process
|
||||||
|
//they belong to.
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := d.target.Valid(); err != nil {
|
if _, err := d.target.Valid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1872,7 +1956,7 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.findLocation(goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
|
return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindLocationSpec will find the location specified by 'locStr' and 'locSpec'.
|
// FindLocationSpec will find the location specified by 'locStr' and 'locSpec'.
|
||||||
@ -1883,22 +1967,29 @@ func (d *Debugger) FindLocationSpec(goid, frame, deferredCall int, locStr string
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
|
if len(d.target.Targets()) != 1 {
|
||||||
|
//TODO(aarzilli): if there is more than one target process all must be
|
||||||
|
//searched and the addresses returned need to specify which target process
|
||||||
|
//they belong to.
|
||||||
|
panic("multiple targets not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := d.target.Valid(); err != nil {
|
if _, err := d.target.Valid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.findLocation(goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
|
return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) findLocation(goid, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
|
func (d *Debugger) findLocation(p *proc.Target, goid, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
|
||||||
s, _ := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
s, _ := proc.ConvertEvalScope(p, goid, frame, deferredCall)
|
||||||
|
|
||||||
locs, err := locSpec.Find(d.target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
|
locs, err := locSpec.Find(p, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
|
||||||
for i := range locs {
|
for i := range locs {
|
||||||
if locs[i].PC == 0 {
|
if locs[i].PC == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
file, line, fn := d.target.BinInfo().PCToLine(locs[i].PC)
|
file, line, fn := p.BinInfo().PCToLine(locs[i].PC)
|
||||||
locs[i].File = file
|
locs[i].File = file
|
||||||
locs[i].Line = line
|
locs[i].Line = line
|
||||||
locs[i].Function = api.ConvertFunction(fn)
|
locs[i].Function = api.ConvertFunction(fn)
|
||||||
@ -1917,7 +2008,7 @@ func (d *Debugger) Disassemble(goroutineID int, addr1, addr2 uint64) ([]proc.Asm
|
|||||||
}
|
}
|
||||||
|
|
||||||
if addr2 == 0 {
|
if addr2 == 0 {
|
||||||
fn := d.target.BinInfo().PCToFunc(addr1)
|
fn := d.target.Selected.BinInfo().PCToFunc(addr1)
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
return nil, fmt.Errorf("address %#x does not belong to any function", addr1)
|
return nil, fmt.Errorf("address %#x does not belong to any function", addr1)
|
||||||
}
|
}
|
||||||
@ -1925,24 +2016,24 @@ func (d *Debugger) Disassemble(goroutineID int, addr1, addr2 uint64) ([]proc.Asm
|
|||||||
addr2 = fn.End
|
addr2 = fn.End
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := proc.FindGoroutine(d.target, goroutineID)
|
g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
curthread := d.target.CurrentThread()
|
curthread := d.target.Selected.CurrentThread()
|
||||||
if g != nil && g.Thread != nil {
|
if g != nil && g.Thread != nil {
|
||||||
curthread = g.Thread
|
curthread = g.Thread
|
||||||
}
|
}
|
||||||
regs, _ := curthread.Registers()
|
regs, _ := curthread.Registers()
|
||||||
|
|
||||||
return proc.Disassemble(d.target.Memory(), regs, d.target.Breakpoints(), d.target.BinInfo(), addr1, addr2)
|
return proc.Disassemble(d.target.Selected.Memory(), regs, d.target.Selected.Breakpoints(), d.target.Selected.BinInfo(), addr1, addr2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) AsmInstructionText(inst *proc.AsmInstruction, flavour proc.AssemblyFlavour) string {
|
func (d *Debugger) AsmInstructionText(inst *proc.AsmInstruction, flavour proc.AssemblyFlavour) string {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return inst.Text(flavour, d.target.BinInfo())
|
return inst.Text(flavour, d.target.Selected.BinInfo())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recorded returns true if the target is a recording.
|
// Recorded returns true if the target is a recording.
|
||||||
@ -1962,7 +2053,7 @@ func (d *Debugger) FindThreadReturnValues(id int, cfg proc.LoadConfig) ([]*proc.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
thread, found := d.target.FindThread(id)
|
thread, found := d.target.Selected.FindThread(id)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, fmt.Errorf("could not find thread %d", id)
|
return nil, fmt.Errorf("could not find thread %d", id)
|
||||||
}
|
}
|
||||||
@ -1995,7 +2086,7 @@ func (d *Debugger) ClearCheckpoint(id int) error {
|
|||||||
func (d *Debugger) ListDynamicLibraries() []*proc.Image {
|
func (d *Debugger) ListDynamicLibraries() []*proc.Image {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.target.BinInfo().Images[1:] // skips the first image because it's the executable file
|
return d.target.Selected.BinInfo().Images[1:] // skips the first image because it's the executable file
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2006,7 +2097,7 @@ func (d *Debugger) ExamineMemory(address uint64, length int) ([]byte, error) {
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
mem := d.target.Memory()
|
mem := d.target.Selected.Memory()
|
||||||
data := make([]byte, length)
|
data := make([]byte, length)
|
||||||
n, err := mem.ReadMemory(data, address)
|
n, err := mem.ReadMemory(data, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2038,7 +2129,7 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !d.isRecording() && !d.IsRunning() {
|
if !d.isRecording() && !d.IsRunning() {
|
||||||
out.TargetGoVersion = d.target.BinInfo().Producer()
|
out.TargetGoVersion = d.target.Selected.BinInfo().Producer()
|
||||||
}
|
}
|
||||||
|
|
||||||
out.MinSupportedVersionOfGo = fmt.Sprintf("%d.%d.0", goversion.MinSupportedVersionOfGoMajor, goversion.MinSupportedVersionOfGoMinor)
|
out.MinSupportedVersionOfGo = fmt.Sprintf("%d.%d.0", goversion.MinSupportedVersionOfGoMajor, goversion.MinSupportedVersionOfGoMinor)
|
||||||
@ -2053,7 +2144,7 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
|
|||||||
func (d *Debugger) ListPackagesBuildInfo(includeFiles bool) []*proc.PackageBuildInfo {
|
func (d *Debugger) ListPackagesBuildInfo(includeFiles bool) []*proc.PackageBuildInfo {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.target.BinInfo().ListPackagesBuildInfo(includeFiles)
|
return d.target.Selected.BinInfo().ListPackagesBuildInfo(includeFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopRecording stops a recording (if one is in progress)
|
// StopRecording stops a recording (if one is in progress)
|
||||||
@ -2072,7 +2163,7 @@ func (d *Debugger) StopRecording() error {
|
|||||||
func (d *Debugger) StopReason() proc.StopReason {
|
func (d *Debugger) StopReason() proc.StopReason {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.target.StopReason
|
return d.target.Selected.StopReason
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockTarget acquires the target mutex.
|
// LockTarget acquires the target mutex.
|
||||||
@ -2090,7 +2181,9 @@ func (d *Debugger) DumpStart(dest string) error {
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
// targetMutex will only be unlocked when the dump is done
|
// targetMutex will only be unlocked when the dump is done
|
||||||
|
|
||||||
if !d.target.CanDump {
|
//TODO(aarzilli): what do we do if the user switches to a different target after starting a dump but before it's finished?
|
||||||
|
|
||||||
|
if !d.target.Selected.CanDump {
|
||||||
d.targetMutex.Unlock()
|
d.targetMutex.Unlock()
|
||||||
return ErrCoreDumpNotSupported
|
return ErrCoreDumpNotSupported
|
||||||
}
|
}
|
||||||
@ -2120,7 +2213,7 @@ func (d *Debugger) DumpStart(dest string) error {
|
|||||||
d.dumpState.Err = nil
|
d.dumpState.Err = nil
|
||||||
go func() {
|
go func() {
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
d.target.Dump(fh, 0, &d.dumpState)
|
d.target.Selected.Dump(fh, 0, &d.dumpState)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -2157,11 +2250,15 @@ func (d *Debugger) DumpCancel() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) Target() *proc.Target {
|
func (d *Debugger) Target() *proc.Target {
|
||||||
|
return d.target.Selected
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Debugger) TargetGroup() *proc.TargetGroup {
|
||||||
return d.target
|
return d.target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) BuildID() string {
|
func (d *Debugger) BuildID() string {
|
||||||
return d.target.BinInfo().BuildID
|
return d.target.Selected.BinInfo().BuildID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) AttachPid() int {
|
func (d *Debugger) AttachPid() int {
|
||||||
@ -2169,13 +2266,13 @@ func (d *Debugger) AttachPid() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult {
|
func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult {
|
||||||
traces := d.target.GetBufferedTracepoints()
|
traces := d.target.Selected.GetBufferedTracepoints()
|
||||||
if traces == nil {
|
if traces == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
results := make([]api.TracepointResult, len(traces))
|
results := make([]api.TracepointResult, len(traces))
|
||||||
for i, trace := range traces {
|
for i, trace := range traces {
|
||||||
f, l, fn := d.target.BinInfo().PCToLine(uint64(trace.FnAddr))
|
f, l, fn := d.target.Selected.BinInfo().PCToLine(uint64(trace.FnAddr))
|
||||||
|
|
||||||
results[i].FunctionName = fn.Name
|
results[i].FunctionName = fn.Name
|
||||||
results[i].Line = l
|
results[i].Line = l
|
||||||
|
|||||||
@ -2,14 +2,9 @@ package debugger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
sys "golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func attachErrorMessage(pid int, err error) error {
|
func attachErrorMessage(pid int, err error) error {
|
||||||
//TODO: mention certificates?
|
//TODO: mention certificates?
|
||||||
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
|
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(pid int) error {
|
|
||||||
return sys.Kill(pid, sys.SIGSTOP)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,13 +2,8 @@ package debugger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
sys "golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func attachErrorMessage(pid int, err error) error {
|
func attachErrorMessage(pid int, err error) error {
|
||||||
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
|
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(pid int) error {
|
|
||||||
return sys.Kill(pid, sys.SIGSTOP)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//lint:file-ignore ST1005 errors here can be capitalized
|
//lint:file-ignore ST1005 errors here can be capitalized
|
||||||
@ -32,7 +30,3 @@ func attachErrorMessage(pid int, err error) error {
|
|||||||
}
|
}
|
||||||
return fallbackerr
|
return fallbackerr
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(pid int) error {
|
|
||||||
return sys.Kill(pid, sys.SIGSTOP)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -14,13 +14,6 @@ func attachErrorMessage(pid int, err error) error {
|
|||||||
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
|
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(pid int) error {
|
|
||||||
// We cannot gracefully stop a process on Windows,
|
|
||||||
// so just ignore this request and let `Detach` kill
|
|
||||||
// the process.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyBinaryFormat(exePath string) error {
|
func verifyBinaryFormat(exePath string) error {
|
||||||
f, err := os.Open(exePath)
|
f, err := os.Open(exePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user