proc,proc/native: adds ability to automatically debug child processes (#3165)
Adds the ability to automatically debug child processes executed by the target to the linux native backend. This commit does not contain user interface or API to access this functionality. Updates #2551
This commit is contained in:
parent
3d6730d12e
commit
37e44bf603
48
_fixtures/spawn.go
Normal file
48
_fixtures/spawn.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func traceme1() {
|
||||||
|
fmt.Printf("parent starting\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceme2(n string) {
|
||||||
|
fmt.Printf("hello from %s\n", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceme3() {
|
||||||
|
fmt.Printf("done\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "spawn":
|
||||||
|
traceme1()
|
||||||
|
n, _ := strconv.Atoi(os.Args[2])
|
||||||
|
cmds := []*exec.Cmd{}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
cmd := exec.Command(exe, "child", fmt.Sprintf("C%d", i))
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Start()
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
traceme3()
|
||||||
|
|
||||||
|
case "child":
|
||||||
|
traceme2(os.Args[2])
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -204,7 +204,7 @@ var ErrUnrecognizedFormat = errors.New("unrecognized core format")
|
|||||||
// OpenCore will open the core file and return a Process struct.
|
// OpenCore will open the core file and return a Process struct.
|
||||||
// If the DWARF information cannot be found in the binary, Delve will look
|
// If the DWARF information cannot be found in the binary, Delve will look
|
||||||
// for external debug files in the directories passed in.
|
// for external debug files in the directories passed in.
|
||||||
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, error) {
|
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||||
var p *process
|
var p *process
|
||||||
var currentThread proc.Thread
|
var currentThread proc.Thread
|
||||||
var err error
|
var err error
|
||||||
@ -222,14 +222,13 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
|
|||||||
return nil, ErrNoThreads
|
return nil, ErrNoThreads
|
||||||
}
|
}
|
||||||
|
|
||||||
return proc.NewTarget(p, p.pid, currentThread, proc.NewTargetConfig{
|
grp, addTarget := proc.NewGroup(p, proc.NewTargetGroupConfig{
|
||||||
Path: exePath,
|
|
||||||
DebugInfoDirs: debugInfoDirs,
|
DebugInfoDirs: debugInfoDirs,
|
||||||
DisableAsyncPreempt: false,
|
DisableAsyncPreempt: false,
|
||||||
StopReason: proc.StopAttached,
|
|
||||||
CanDump: false,
|
CanDump: false,
|
||||||
ContinueOnce: continueOnce,
|
|
||||||
})
|
})
|
||||||
|
_, err = addTarget(p, p.pid, currentThread, exePath, proc.StopAttached)
|
||||||
|
return grp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinInfo will return the binary info.
|
// BinInfo will return the binary info.
|
||||||
@ -310,6 +309,11 @@ func (p *process) WriteMemory(addr uint64, data []byte) (int, error) {
|
|||||||
return 0, ErrWriteCore
|
return 0, ErrWriteCore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FollowExec enables (or disables) follow exec mode
|
||||||
|
func (p *process) FollowExec(bool) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessMemory returns the memory of this thread's process.
|
// ProcessMemory returns the memory of this thread's process.
|
||||||
func (t *thread) ProcessMemory() proc.MemoryReadWriter {
|
func (t *thread) ProcessMemory() proc.MemoryReadWriter {
|
||||||
return t.p
|
return t.p
|
||||||
@ -419,7 +423,7 @@ func (p *process) ClearInternalBreakpoints() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
func (*process) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
||||||
return nil, proc.StopUnknown, ErrContinueCore
|
return nil, proc.StopUnknown, ErrContinueCore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ func TestSplicedReader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withCoreFile(t *testing.T, name, args string) *proc.Target {
|
func withCoreFile(t *testing.T, name, args string) *proc.TargetGroup {
|
||||||
// This is all very fragile and won't work on hosts with non-default core patterns.
|
// This is all very fragile and won't work on hosts with non-default core patterns.
|
||||||
// Might be better to check in the binary and core?
|
// Might be better to check in the binary and core?
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
@ -251,8 +251,8 @@ func TestCore(t *testing.T) {
|
|||||||
if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
|
if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
|
||||||
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
|
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
|
||||||
}
|
}
|
||||||
p := withCoreFile(t, "panic", "")
|
grp := withCoreFile(t, "panic", "")
|
||||||
grp := proc.NewGroup(p)
|
p := grp.Selected
|
||||||
|
|
||||||
recorded, _ := grp.Recorded()
|
recorded, _ := grp.Recorded()
|
||||||
if !recorded {
|
if !recorded {
|
||||||
@ -324,7 +324,8 @@ func TestCoreFpRegisters(t *testing.T) {
|
|||||||
t.Skip("not supported in go1.10 and later")
|
t.Skip("not supported in go1.10 and later")
|
||||||
}
|
}
|
||||||
|
|
||||||
p := withCoreFile(t, "fputest/", "panic")
|
grp := withCoreFile(t, "fputest/", "panic")
|
||||||
|
p := grp.Selected
|
||||||
|
|
||||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||||
if err != nil || len(gs) == 0 {
|
if err != nil || len(gs) == 0 {
|
||||||
@ -407,7 +408,8 @@ func TestCoreWithEmptyString(t *testing.T) {
|
|||||||
if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
|
if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" {
|
||||||
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
|
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
|
||||||
}
|
}
|
||||||
p := withCoreFile(t, "coreemptystring", "")
|
grp := withCoreFile(t, "coreemptystring", "")
|
||||||
|
p := grp.Selected
|
||||||
|
|
||||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||||
assertNoError(err, t, "GoroutinesInfo")
|
assertNoError(err, t, "GoroutinesInfo")
|
||||||
@ -452,10 +454,11 @@ func TestMinidump(t *testing.T) {
|
|||||||
fix := test.BuildFixture("sleep", buildFlags)
|
fix := test.BuildFixture("sleep", buildFlags)
|
||||||
mdmpPath := procdump(t, fix.Path)
|
mdmpPath := procdump(t, fix.Path)
|
||||||
|
|
||||||
p, err := OpenCore(mdmpPath, fix.Path, []string{})
|
grp, err := OpenCore(mdmpPath, fix.Path, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("OpenCore: %v", err)
|
t.Fatalf("OpenCore: %v", err)
|
||||||
}
|
}
|
||||||
|
p := grp.Selected
|
||||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||||
if err != nil || len(gs) == 0 {
|
if err != nil || len(gs) == 0 {
|
||||||
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
|
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
|
||||||
|
@ -278,7 +278,7 @@ func newProcess(process *os.Process) *gdbProcess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen waits for a connection from the stub.
|
// Listen waits for a connection from the stub.
|
||||||
func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||||
acceptChan := make(chan net.Conn)
|
acceptChan := make(chan net.Conn)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -300,7 +300,7 @@ func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dial attempts to connect to the stub.
|
// Dial attempts to connect to the stub.
|
||||||
func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||||
for {
|
for {
|
||||||
conn, err := net.Dial("tcp", addr)
|
conn, err := net.Dial("tcp", addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -321,7 +321,7 @@ func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []str
|
|||||||
// program and the PID of the target process, both are optional, however
|
// program and the PID of the target process, both are optional, however
|
||||||
// some stubs do not provide ways to determine path and pid automatically
|
// some stubs do not provide ways to determine path and pid automatically
|
||||||
// and Connect will be unable to function without knowing them.
|
// and Connect will be unable to function without knowing them.
|
||||||
func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||||
p.conn.conn = conn
|
p.conn.conn = conn
|
||||||
p.conn.pid = pid
|
p.conn.pid = pid
|
||||||
err := p.conn.handshake(p.regnames)
|
err := p.conn.handshake(p.regnames)
|
||||||
@ -450,7 +450,7 @@ func getLdEnvVars() []string {
|
|||||||
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
||||||
// it to launch the specified target program with the specified arguments
|
// it to launch the specified target program with the specified arguments
|
||||||
// (cmd) on the specified directory wd.
|
// (cmd) on the specified directory wd.
|
||||||
func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
|
func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return nil, ErrUnsupportedOS
|
return nil, ErrUnsupportedOS
|
||||||
}
|
}
|
||||||
@ -567,11 +567,11 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [
|
|||||||
p := newProcess(process.Process)
|
p := newProcess(process.Process)
|
||||||
p.conn.isDebugserver = isDebugserver
|
p.conn.isDebugserver = isDebugserver
|
||||||
|
|
||||||
var tgt *proc.Target
|
var grp *proc.TargetGroup
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
tgt, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
grp, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
||||||
} else {
|
} else {
|
||||||
tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
grp, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
||||||
}
|
}
|
||||||
if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) {
|
if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) {
|
||||||
// Make the target process the controlling process of the tty if it is a foreground process.
|
// Make the target process the controlling process of the tty if it is a foreground process.
|
||||||
@ -580,7 +580,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [
|
|||||||
logflags.DebuggerLogger().Errorf("could not set controlling process: %v", err)
|
logflags.DebuggerLogger().Errorf("could not set controlling process: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tgt, err
|
return grp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLDBAttach starts an instance of lldb-server and connects to it, asking
|
// LLDBAttach starts an instance of lldb-server and connects to it, asking
|
||||||
@ -588,7 +588,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [
|
|||||||
// Path is path to the target's executable, path only needs to be specified
|
// Path is path to the target's executable, path only needs to be specified
|
||||||
// for some stubs that do not provide an automated way of determining it
|
// for some stubs that do not provide an automated way of determining it
|
||||||
// (for example debugserver).
|
// (for example debugserver).
|
||||||
func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, error) {
|
func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return nil, ErrUnsupportedOS
|
return nil, ErrUnsupportedOS
|
||||||
}
|
}
|
||||||
@ -633,13 +633,13 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
|
|||||||
p := newProcess(process.Process)
|
p := newProcess(process.Process)
|
||||||
p.conn.isDebugserver = isDebugserver
|
p.conn.isDebugserver = isDebugserver
|
||||||
|
|
||||||
var tgt *proc.Target
|
var grp *proc.TargetGroup
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
tgt, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached)
|
grp, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached)
|
||||||
} else {
|
} else {
|
||||||
tgt, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached)
|
grp, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached)
|
||||||
}
|
}
|
||||||
return tgt, err
|
return grp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntryPoint will return the process entry point address, useful for
|
// EntryPoint will return the process entry point address, useful for
|
||||||
@ -673,7 +673,7 @@ func (p *gdbProcess) EntryPoint() (uint64, error) {
|
|||||||
// initialize uses qProcessInfo to load the inferior's PID and
|
// initialize uses qProcessInfo to load the inferior's PID and
|
||||||
// executable path. This command is not supported by all stubs and not all
|
// executable path. This command is not supported by all stubs and not all
|
||||||
// stubs will report both the PID and executable path.
|
// stubs will report both the PID and executable path.
|
||||||
func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
|
func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||||
var err error
|
var err error
|
||||||
if path == "" {
|
if path == "" {
|
||||||
// If we are attaching to a running process and the user didn't specify
|
// If we are attaching to a running process and the user didn't specify
|
||||||
@ -724,19 +724,18 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tgt, err := proc.NewTarget(p, p.conn.pid, p.currentThread, proc.NewTargetConfig{
|
grp, addTarget := proc.NewGroup(p, proc.NewTargetGroupConfig{
|
||||||
Path: path,
|
|
||||||
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,
|
|
||||||
})
|
})
|
||||||
|
_, err = addTarget(p, p.conn.pid, p.currentThread, path, stopReason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Detach(true)
|
p.Detach(true)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return tgt, nil
|
return grp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryProcessInfo(p *gdbProcess, pid int) (int, string, error) {
|
func queryProcessInfo(p *gdbProcess, pid int) (int, string, error) {
|
||||||
@ -821,11 +820,7 @@ const (
|
|||||||
debugServerTargetExcBreakpoint = 0x96
|
debugServerTargetExcBreakpoint = 0x96
|
||||||
)
|
)
|
||||||
|
|
||||||
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
func (p *gdbProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
||||||
if len(procs) != 1 {
|
|
||||||
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}
|
||||||
}
|
}
|
||||||
@ -1307,6 +1302,11 @@ func (p *gdbProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
|
|||||||
return p.conn.clearBreakpoint(bp.Addr, watchTypeToBreakpointType(bp.WatchType), kind)
|
return p.conn.clearBreakpoint(bp.Addr, watchTypeToBreakpointType(bp.WatchType), kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FollowExec enables (or disables) follow exec mode
|
||||||
|
func (p *gdbProcess) FollowExec(bool) error {
|
||||||
|
return errors.New("follow exec not supported")
|
||||||
|
}
|
||||||
|
|
||||||
type threadUpdater struct {
|
type threadUpdater struct {
|
||||||
p *gdbProcess
|
p *gdbProcess
|
||||||
seen map[int]bool
|
seen map[int]bool
|
||||||
|
@ -124,7 +124,7 @@ func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir
|
|||||||
|
|
||||||
// Replay starts an instance of rr in replay mode, with the specified trace
|
// Replay starts an instance of rr in replay mode, with the specified trace
|
||||||
// directory, and connects to it.
|
// directory, and connects to it.
|
||||||
func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.Target, error) {
|
func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||||
if err := checkRRAvailable(); err != nil {
|
if err := checkRRAvailable(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ func rrParseGdbCommand(line string) rrInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordAndReplay acts like calling Record and then Replay.
|
// RecordAndReplay acts like calling Record and then Replay.
|
||||||
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) {
|
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.TargetGroup, string, error) {
|
||||||
tracedir, err := Record(cmd, wd, quiet, redirects)
|
tracedir, err := Record(cmd, wd, quiet, redirects)
|
||||||
if tracedir == "" {
|
if tracedir == "" {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
@ -30,14 +30,12 @@ func withTestRecording(name string, t testing.TB, fn func(grp *proc.TargetGroup,
|
|||||||
t.Skip("test skipped, rr not found")
|
t.Skip("test skipped, rr not found")
|
||||||
}
|
}
|
||||||
t.Log("recording")
|
t.Log("recording")
|
||||||
p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{})
|
grp, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Launch():", err)
|
t.Fatal("Launch():", err)
|
||||||
}
|
}
|
||||||
t.Logf("replaying %q", tracedir)
|
t.Logf("replaying %q", tracedir)
|
||||||
|
|
||||||
grp := proc.NewGroup(p)
|
|
||||||
|
|
||||||
defer grp.Detach(true)
|
defer grp.Detach(true)
|
||||||
|
|
||||||
fn(grp, fixture)
|
fn(grp, fixture)
|
||||||
|
@ -7,6 +7,11 @@ import (
|
|||||||
"github.com/go-delve/delve/pkg/proc/internal/ebpf"
|
"github.com/go-delve/delve/pkg/proc/internal/ebpf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProcessGroup is a group of processes that are resumed at the same time.
|
||||||
|
type ProcessGroup interface {
|
||||||
|
ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Process represents the target of the debugger. This
|
// Process represents the target of the debugger. This
|
||||||
// target could be a system process, core file, etc.
|
// target could be a system process, core file, etc.
|
||||||
//
|
//
|
||||||
@ -57,6 +62,9 @@ type ProcessInternal interface {
|
|||||||
|
|
||||||
// StartCallInjection notifies the backend that we are about to inject a function call.
|
// StartCallInjection notifies the backend that we are about to inject a function call.
|
||||||
StartCallInjection() (func(), error)
|
StartCallInjection() (func(), error)
|
||||||
|
|
||||||
|
// FollowExec enables (or disables) follow exec mode
|
||||||
|
FollowExec(bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordingManipulation is an interface for manipulating process recordings.
|
// RecordingManipulation is an interface for manipulating process recordings.
|
||||||
|
11
pkg/proc/native/followexec_other.go
Normal file
11
pkg/proc/native/followexec_other.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package native
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// FollowExec enables (or disables) follow exec mode
|
||||||
|
func (*nativeProcess) FollowExec(bool) error {
|
||||||
|
return errors.New("follow exec not implemented")
|
||||||
|
}
|
@ -16,12 +16,12 @@ import (
|
|||||||
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
||||||
|
|
||||||
// Launch returns ErrNativeBackendDisabled.
|
// Launch returns ErrNativeBackendDisabled.
|
||||||
func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) {
|
func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) {
|
||||||
return nil, ErrNativeBackendDisabled
|
return nil, ErrNativeBackendDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach returns ErrNativeBackendDisabled.
|
// Attach returns ErrNativeBackendDisabled.
|
||||||
func Attach(_ int, _ []string) (*proc.Target, error) {
|
func Attach(_ int, _ []string) (*proc.TargetGroup, error) {
|
||||||
return nil, ErrNativeBackendDisabled
|
return nil, ErrNativeBackendDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,11 +57,11 @@ func (dbp *nativeProcess) resume() error {
|
|||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
func (*processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +24,11 @@ type nativeProcess struct {
|
|||||||
// Thread used to read and write memory
|
// Thread used to read and write memory
|
||||||
memthread *nativeThread
|
memthread *nativeThread
|
||||||
|
|
||||||
os *osProcessDetails
|
os *osProcessDetails
|
||||||
firstStart bool
|
firstStart bool
|
||||||
ptraceChan chan func()
|
ptraceThread *ptraceThread
|
||||||
ptraceDoneChan chan interface{}
|
childProcess bool // this process was launched, not attached to
|
||||||
childProcess bool // this process was launched, not attached to
|
followExec bool // automatically attach to new processes
|
||||||
|
|
||||||
// Controlling terminal file descriptor for
|
// Controlling terminal file descriptor for
|
||||||
// this process.
|
// this process.
|
||||||
@ -45,19 +45,30 @@ type nativeProcess struct {
|
|||||||
// `handlePtraceFuncs`.
|
// `handlePtraceFuncs`.
|
||||||
func newProcess(pid int) *nativeProcess {
|
func newProcess(pid int) *nativeProcess {
|
||||||
dbp := &nativeProcess{
|
dbp := &nativeProcess{
|
||||||
pid: pid,
|
pid: pid,
|
||||||
threads: make(map[int]*nativeThread),
|
threads: make(map[int]*nativeThread),
|
||||||
breakpoints: proc.NewBreakpointMap(),
|
breakpoints: proc.NewBreakpointMap(),
|
||||||
firstStart: true,
|
firstStart: true,
|
||||||
os: new(osProcessDetails),
|
os: new(osProcessDetails),
|
||||||
ptraceChan: make(chan func()),
|
ptraceThread: newPtraceThread(),
|
||||||
ptraceDoneChan: make(chan interface{}),
|
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
|
||||||
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
|
|
||||||
}
|
}
|
||||||
go dbp.handlePtraceFuncs()
|
|
||||||
return dbp
|
return dbp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newChildProcess is like newProcess but uses the same ptrace thread as dbp.
|
||||||
|
func newChildProcess(dbp *nativeProcess, pid int) *nativeProcess {
|
||||||
|
return &nativeProcess{
|
||||||
|
pid: pid,
|
||||||
|
threads: make(map[int]*nativeThread),
|
||||||
|
breakpoints: proc.NewBreakpointMap(),
|
||||||
|
firstStart: true,
|
||||||
|
os: new(osProcessDetails),
|
||||||
|
ptraceThread: dbp.ptraceThread.acquire(),
|
||||||
|
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BinInfo will return the binary info struct associated with this process.
|
// BinInfo will return the binary info struct associated with this process.
|
||||||
func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo {
|
func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo {
|
||||||
return dbp.bi
|
return dbp.bi
|
||||||
@ -172,23 +183,58 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
|
|||||||
return dbp.memthread.clearSoftwareBreakpoint(bp)
|
return dbp.memthread.clearSoftwareBreakpoint(bp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
type processGroup struct {
|
||||||
if len(procs) != 1 {
|
procs []*nativeProcess
|
||||||
|
addTarget proc.AddTargetFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (procgrp *processGroup) numValid() int {
|
||||||
|
n := 0
|
||||||
|
for _, p := range procgrp.procs {
|
||||||
|
if ok, _ := p.Valid(); ok {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (procgrp *processGroup) procForThread(tid int) *nativeProcess {
|
||||||
|
for _, p := range procgrp.procs {
|
||||||
|
if p.threads[tid] != nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason) (*proc.Target, error) {
|
||||||
|
tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
procgrp.procs = append(procgrp.procs, p)
|
||||||
|
return tgt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
|
||||||
|
if len(procgrp.procs) != 1 && runtime.GOOS != "linux" {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
dbp := procs[0].(*nativeProcess)
|
if procgrp.numValid() == 0 {
|
||||||
if dbp.exited {
|
return nil, proc.StopExited, proc.ErrProcessExited{Pid: procgrp.procs[0].pid}
|
||||||
return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
for _, dbp := range procgrp.procs {
|
||||||
if err := dbp.resume(); err != nil {
|
if dbp.exited {
|
||||||
return nil, proc.StopUnknown, err
|
continue
|
||||||
}
|
}
|
||||||
|
if err := dbp.resume(); err != nil {
|
||||||
for _, th := range dbp.threads {
|
return nil, proc.StopUnknown, err
|
||||||
th.CurrentBreakpoint.Clear()
|
}
|
||||||
|
for _, th := range dbp.threads {
|
||||||
|
th.CurrentBreakpoint.Clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cctx.ResumeChan != nil {
|
if cctx.ResumeChan != nil {
|
||||||
@ -196,16 +242,29 @@ func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext)
|
|||||||
cctx.ResumeChan = nil
|
cctx.ResumeChan = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
trapthread, err := dbp.trapWait(-1)
|
trapthread, err := trapWait(procgrp, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, proc.StopUnknown, err
|
return nil, proc.StopUnknown, err
|
||||||
}
|
}
|
||||||
trapthread, err = dbp.stop(cctx, trapthread)
|
trapthread, err = procgrp.stop(cctx, trapthread)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, proc.StopUnknown, err
|
return nil, proc.StopUnknown, err
|
||||||
}
|
}
|
||||||
if trapthread != nil {
|
if trapthread != nil {
|
||||||
|
dbp := procgrp.procForThread(trapthread.ID)
|
||||||
dbp.memthread = trapthread
|
dbp.memthread = trapthread
|
||||||
|
// refresh memthread for every other process
|
||||||
|
for _, p2 := range procgrp.procs {
|
||||||
|
if p2.exited || p2 == dbp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, th := range p2.threads {
|
||||||
|
p2.memthread = th
|
||||||
|
if th.SoftExc() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return trapthread, proc.StopUnknown, nil
|
return trapthread, proc.StopUnknown, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,21 +285,26 @@ func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakp
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize will ensure that all relevant information is loaded
|
func (dbp *nativeProcess) initializeBasic() error {
|
||||||
// so the process is ready to be debugged.
|
|
||||||
func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.Target, error) {
|
|
||||||
if err := initialize(dbp); err != nil {
|
if err := initialize(dbp); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
if err := dbp.updateThreadList(); err != nil {
|
if err := dbp.updateThreadList(); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize will ensure that all relevant information is loaded
|
||||||
|
// so the process is ready to be debugged.
|
||||||
|
func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||||
|
dbp.initializeBasic()
|
||||||
stopReason := proc.StopLaunched
|
stopReason := proc.StopLaunched
|
||||||
if !dbp.childProcess {
|
if !dbp.childProcess {
|
||||||
stopReason = proc.StopAttached
|
stopReason = proc.StopAttached
|
||||||
}
|
}
|
||||||
tgt, err := proc.NewTarget(dbp, dbp.pid, dbp.memthread, proc.NewTargetConfig{
|
procgrp := &processGroup{}
|
||||||
Path: path,
|
grp, addTarget := proc.NewGroup(procgrp, proc.NewTargetGroupConfig{
|
||||||
DebugInfoDirs: debugInfoDirs,
|
DebugInfoDirs: debugInfoDirs,
|
||||||
|
|
||||||
// We disable asyncpreempt for the following reasons:
|
// We disable asyncpreempt for the following reasons:
|
||||||
@ -253,20 +317,21 @@ 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 == "linux" && runtime.GOARCH == "arm64"),
|
DisableAsyncPreempt: runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"),
|
||||||
|
|
||||||
StopReason: stopReason,
|
StopReason: stopReason,
|
||||||
CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"),
|
CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"),
|
||||||
ContinueOnce: continueOnce,
|
|
||||||
})
|
})
|
||||||
|
procgrp.addTarget = addTarget
|
||||||
|
tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if dbp.bi.Arch.Name == "arm64" {
|
if dbp.bi.Arch.Name == "arm64" {
|
||||||
dbp.iscgo = tgt.IsCgo()
|
dbp.iscgo = tgt.IsCgo()
|
||||||
}
|
}
|
||||||
return tgt, nil
|
return grp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) handlePtraceFuncs() {
|
func (pt *ptraceThread) handlePtraceFuncs() {
|
||||||
// We must ensure here that we are running on the same thread during
|
// We must ensure here that we are running on the same thread during
|
||||||
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
|
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
|
||||||
// all commands after PTRACE_ATTACH to come from the same thread.
|
// all commands after PTRACE_ATTACH to come from the same thread.
|
||||||
@ -279,21 +344,20 @@ func (dbp *nativeProcess) handlePtraceFuncs() {
|
|||||||
defer runtime.UnlockOSThread()
|
defer runtime.UnlockOSThread()
|
||||||
}
|
}
|
||||||
|
|
||||||
for fn := range dbp.ptraceChan {
|
for fn := range pt.ptraceChan {
|
||||||
fn()
|
fn()
|
||||||
dbp.ptraceDoneChan <- nil
|
pt.ptraceDoneChan <- nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) execPtraceFunc(fn func()) {
|
func (dbp *nativeProcess) execPtraceFunc(fn func()) {
|
||||||
dbp.ptraceChan <- fn
|
dbp.ptraceThread.ptraceChan <- fn
|
||||||
<-dbp.ptraceDoneChan
|
<-dbp.ptraceThread.ptraceDoneChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) postExit() {
|
func (dbp *nativeProcess) postExit() {
|
||||||
dbp.exited = true
|
dbp.exited = true
|
||||||
close(dbp.ptraceChan)
|
dbp.ptraceThread.release()
|
||||||
close(dbp.ptraceDoneChan)
|
|
||||||
dbp.bi.Close()
|
dbp.bi.Close()
|
||||||
if dbp.ctty != nil {
|
if dbp.ctty != nil {
|
||||||
dbp.ctty.Close()
|
dbp.ctty.Close()
|
||||||
@ -349,3 +413,32 @@ func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr
|
|||||||
|
|
||||||
return stdin, stdout, stderr, closefn, nil
|
return stdin, stdout, stderr, closefn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ptraceThread struct {
|
||||||
|
ptraceRefCnt int
|
||||||
|
ptraceChan chan func()
|
||||||
|
ptraceDoneChan chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPtraceThread() *ptraceThread {
|
||||||
|
pt := &ptraceThread{
|
||||||
|
ptraceChan: make(chan func()),
|
||||||
|
ptraceDoneChan: make(chan interface{}),
|
||||||
|
ptraceRefCnt: 1,
|
||||||
|
}
|
||||||
|
go pt.handlePtraceFuncs()
|
||||||
|
return pt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *ptraceThread) acquire() *ptraceThread {
|
||||||
|
pt.ptraceRefCnt++
|
||||||
|
return pt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *ptraceThread) release() {
|
||||||
|
pt.ptraceRefCnt--
|
||||||
|
if pt.ptraceRefCnt == 0 {
|
||||||
|
close(pt.ptraceChan)
|
||||||
|
close(pt.ptraceDoneChan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -42,7 +42,7 @@ func (os *osProcessDetails) Close() {}
|
|||||||
// custom fork/exec process in order to take advantage of
|
// custom fork/exec process in order to take advantage of
|
||||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||||
// Mach exceptions.
|
// Mach exceptions.
|
||||||
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) {
|
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) {
|
||||||
argv0Go, err := filepath.Abs(cmd[0])
|
argv0Go, err := filepath.Abs(cmd[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -121,7 +121,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, err := dbp.stop(nil, nil); err != nil {
|
if _, err := dbp.stop(nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach to an existing process with the given PID.
|
// Attach to an existing process with the given PID.
|
||||||
func Attach(pid int, _ []string) (*proc.Target, error) {
|
func Attach(pid int, _ []string) (*proc.TargetGroup, error) {
|
||||||
if err := macutil.CheckRosetta(); err != nil {
|
if err := macutil.CheckRosetta(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -291,6 +291,10 @@ func findExecutable(path string, pid int) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
|
||||||
|
return procgrp.procs[0].trapWait(pid)
|
||||||
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
||||||
for {
|
for {
|
||||||
task := dbp.os.task
|
task := dbp.os.task
|
||||||
@ -429,7 +433,11 @@ func (dbp *nativeProcess) resume() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stop stops all running threads and sets breakpoints
|
// stop stops all running threads and sets breakpoints
|
||||||
func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
||||||
|
return procgrp.procs[0].stop(trapthread)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
|
||||||
if dbp.exited {
|
if dbp.exited {
|
||||||
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ func (os *osProcessDetails) Close() {}
|
|||||||
// to be supplied to that process. `wd` is working directory of the program.
|
// to be supplied to that process. `wd` is working directory of the program.
|
||||||
// If the DWARF information cannot be found in the binary, Delve will look
|
// If the DWARF information cannot be found in the binary, Delve will look
|
||||||
// for external debug files in the directories passed in.
|
// for external debug files in the directories passed in.
|
||||||
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
|
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) {
|
||||||
var (
|
var (
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
err error
|
err error
|
||||||
@ -121,7 +121,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str
|
|||||||
// Attach to an existing process with the given PID. Once attached, if
|
// Attach to an existing process with the given PID. Once attached, if
|
||||||
// the DWARF information cannot be found in the binary, Delve will look
|
// the DWARF information cannot be found in the binary, Delve will look
|
||||||
// for external debug files in the directories passed in.
|
// for external debug files in the directories passed in.
|
||||||
func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
|
func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||||
dbp := newProcess(pid)
|
dbp := newProcess(pid)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -227,8 +227,8 @@ func findExecutable(path string, pid int) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
|
||||||
return dbp.trapWaitInternal(pid, trapWaitNormal)
|
return procgrp.procs[0].trapWaitInternal(pid, trapWaitNormal)
|
||||||
}
|
}
|
||||||
|
|
||||||
type trapWaitMode uint8
|
type trapWaitMode uint8
|
||||||
@ -403,7 +403,11 @@ func (dbp *nativeProcess) resume() error {
|
|||||||
|
|
||||||
// Used by ContinueOnce
|
// Used by ContinueOnce
|
||||||
// stop stops all running threads and sets breakpoints
|
// stop stops all running threads and sets breakpoints
|
||||||
func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
||||||
|
return procgrp.procs[0].stop(trapthread)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
|
||||||
if dbp.exited {
|
if dbp.exited {
|
||||||
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func (os *osProcessDetails) Close() {
|
|||||||
// to be supplied to that process. `wd` is working directory of the program.
|
// to be supplied to that process. `wd` is working directory of the program.
|
||||||
// If the DWARF information cannot be found in the binary, Delve will look
|
// If the DWARF information cannot be found in the binary, Delve will look
|
||||||
// for external debug files in the directories passed in.
|
// for external debug files in the directories passed in.
|
||||||
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
|
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) {
|
||||||
var (
|
var (
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
err error
|
err error
|
||||||
@ -141,7 +141,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str
|
|||||||
// Attach to an existing process with the given PID. Once attached, if
|
// Attach to an existing process with the given PID. Once attached, if
|
||||||
// the DWARF information cannot be found in the binary, Delve will look
|
// the DWARF information cannot be found in the binary, Delve will look
|
||||||
// for external debug files in the directories passed in.
|
// for external debug files in the directories passed in.
|
||||||
func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
|
func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||||
dbp := newProcess(pid)
|
dbp := newProcess(pid)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -237,6 +237,11 @@ func (dbp *nativeProcess) requestManualStop() (err error) {
|
|||||||
return sys.Kill(dbp.pid, sys.SIGTRAP)
|
return sys.Kill(dbp.pid, sys.SIGTRAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ptraceOptionsNormal = syscall.PTRACE_O_TRACECLONE
|
||||||
|
ptraceOptionsFollowExec = syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEVFORK | syscall.PTRACE_O_TRACEEXEC
|
||||||
|
)
|
||||||
|
|
||||||
// Attach to a newly created thread, and store that thread in our list of
|
// Attach to a newly created thread, and store that thread in our list of
|
||||||
// known threads.
|
// known threads.
|
||||||
func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
|
func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
|
||||||
@ -244,6 +249,11 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error)
|
|||||||
return thread, nil
|
return thread, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ptraceOptions := ptraceOptionsNormal
|
||||||
|
if dbp.followExec {
|
||||||
|
ptraceOptions = ptraceOptionsFollowExec
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if attach {
|
if attach {
|
||||||
dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
|
dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
|
||||||
@ -263,12 +273,12 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
|
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })
|
||||||
if err == syscall.ESRCH {
|
if err == syscall.ESRCH {
|
||||||
if _, _, err = dbp.waitFast(tid); err != nil {
|
if _, _, err = dbp.waitFast(tid); err != nil {
|
||||||
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
|
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
|
||||||
}
|
}
|
||||||
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
|
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })
|
||||||
if err == syscall.ESRCH {
|
if err == syscall.ESRCH {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -318,8 +328,8 @@ func findExecutable(path string, pid int) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
|
||||||
return dbp.trapWaitInternal(pid, 0)
|
return trapWaitInternal(procgrp, pid, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type trapWaitOptions uint8
|
type trapWaitOptions uint8
|
||||||
@ -330,14 +340,21 @@ const (
|
|||||||
trapWaitDontCallExitGuard
|
trapWaitDontCallExitGuard
|
||||||
)
|
)
|
||||||
|
|
||||||
func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*nativeThread, error) {
|
func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (*nativeThread, error) {
|
||||||
|
var waitdbp *nativeProcess = nil
|
||||||
|
if len(procgrp.procs) == 1 {
|
||||||
|
// Note that waitdbp is only used to call (*nativeProcess).wait which will
|
||||||
|
// behave correctly if waitdbp == nil.
|
||||||
|
waitdbp = procgrp.procs[0]
|
||||||
|
}
|
||||||
|
|
||||||
halt := options&trapWaitHalt != 0
|
halt := options&trapWaitHalt != 0
|
||||||
for {
|
for {
|
||||||
wopt := 0
|
wopt := 0
|
||||||
if options&trapWaitNohang != 0 {
|
if options&trapWaitNohang != 0 {
|
||||||
wopt = sys.WNOHANG
|
wopt = sys.WNOHANG
|
||||||
}
|
}
|
||||||
wpid, status, err := dbp.wait(pid, wopt)
|
wpid, status, err := waitdbp.wait(pid, wopt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("wait err %s %d", err, pid)
|
return nil, fmt.Errorf("wait err %s %d", err, pid)
|
||||||
}
|
}
|
||||||
@ -347,14 +364,27 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
th, ok := dbp.threads[wpid]
|
dbp := procgrp.procForThread(wpid)
|
||||||
if ok {
|
var th *nativeThread
|
||||||
th.Status = (*waitStatus)(status)
|
if dbp != nil {
|
||||||
|
var ok bool
|
||||||
|
th, ok = dbp.threads[wpid]
|
||||||
|
if ok {
|
||||||
|
th.Status = (*waitStatus)(status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dbp = procgrp.procs[0]
|
||||||
}
|
}
|
||||||
if status.Exited() {
|
if status.Exited() {
|
||||||
if wpid == dbp.pid {
|
if wpid == dbp.pid {
|
||||||
dbp.postExit()
|
dbp.postExit()
|
||||||
return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
|
if procgrp.numValid() == 0 {
|
||||||
|
return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
|
||||||
|
}
|
||||||
|
if halt {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
delete(dbp.threads, wpid)
|
delete(dbp.threads, wpid)
|
||||||
continue
|
continue
|
||||||
@ -363,15 +393,24 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n
|
|||||||
// Signaled means the thread was terminated due to a signal.
|
// Signaled means the thread was terminated due to a signal.
|
||||||
if wpid == dbp.pid {
|
if wpid == dbp.pid {
|
||||||
dbp.postExit()
|
dbp.postExit()
|
||||||
return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
|
if procgrp.numValid() == 0 {
|
||||||
|
return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
|
||||||
|
}
|
||||||
|
if halt {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
// does this ever happen?
|
// does this ever happen?
|
||||||
delete(dbp.threads, wpid)
|
delete(dbp.threads, wpid)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
|
if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_CLONE || status.TrapCause() == sys.PTRACE_EVENT_VFORK) {
|
||||||
// A traced thread has cloned a new thread, grab the pid and
|
// A traced thread has cloned a new thread, grab the pid and
|
||||||
// add it to our list of traced threads.
|
// add it to our list of traced threads.
|
||||||
|
// If TrapCause() is sys.PTRACE_EVENT_VFORK this is actually a new
|
||||||
|
// process, but treat it as a normal thread until exec happens, so that
|
||||||
|
// we can initialize the new process normally.
|
||||||
var cloned uint
|
var cloned uint
|
||||||
dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) })
|
dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -410,6 +449,34 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_EXEC) {
|
||||||
|
// A thread called exec and we now have a new process. Retrieve the
|
||||||
|
// thread ID of the exec'ing thread with PtraceGetEventMsg to remove it
|
||||||
|
// and create a new nativeProcess object to track the new process.
|
||||||
|
var tid uint
|
||||||
|
dbp.execPtraceFunc(func() { tid, err = sys.PtraceGetEventMsg(wpid) })
|
||||||
|
if err == nil {
|
||||||
|
delete(dbp.threads, int(tid))
|
||||||
|
}
|
||||||
|
dbp = newChildProcess(procgrp.procs[0], wpid)
|
||||||
|
dbp.followExec = true
|
||||||
|
dbp.initializeBasic()
|
||||||
|
_, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched)
|
||||||
|
if err != nil {
|
||||||
|
_ = dbp.Detach(false)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if halt {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// TODO(aarzilli): if we want to give users the ability to stop the target
|
||||||
|
// group on exec here is where we should return
|
||||||
|
err = dbp.threads[dbp.pid].Continue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if th == nil {
|
if th == nil {
|
||||||
// Sometimes we get an unknown thread, ignore it?
|
// Sometimes we get an unknown thread, ignore it?
|
||||||
continue
|
continue
|
||||||
@ -439,7 +506,10 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n
|
|||||||
// do the same thing we do if a thread quit
|
// do the same thing we do if a thread quit
|
||||||
if wpid == dbp.pid {
|
if wpid == dbp.pid {
|
||||||
dbp.postExit()
|
dbp.postExit()
|
||||||
return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
|
if procgrp.numValid() == 0 {
|
||||||
|
return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
delete(dbp.threads, wpid)
|
delete(dbp.threads, wpid)
|
||||||
}
|
}
|
||||||
@ -476,7 +546,7 @@ func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) {
|
|||||||
|
|
||||||
func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
||||||
var s sys.WaitStatus
|
var s sys.WaitStatus
|
||||||
if (pid != dbp.pid) || (options != 0) {
|
if (dbp == nil) || (pid != dbp.pid) || (options != 0) {
|
||||||
wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
|
wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
|
||||||
return wpid, &s, err
|
return wpid, &s, err
|
||||||
}
|
}
|
||||||
@ -506,12 +576,12 @@ func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) exitGuard(err error) error {
|
func exitGuard(dbp *nativeProcess, procgrp *processGroup, err error) error {
|
||||||
if err != sys.ESRCH {
|
if err != sys.ESRCH {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status(dbp.pid, dbp.os.comm) == statusZombie {
|
if status(dbp.pid, dbp.os.comm) == statusZombie {
|
||||||
_, err := dbp.trapWaitInternal(-1, trapWaitDontCallExitGuard)
|
_, err := trapWaitInternal(procgrp, -1, trapWaitDontCallExitGuard)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,21 +608,27 @@ func (dbp *nativeProcess) resume() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stop stops all running threads and sets breakpoints
|
// stop stops all running threads and sets breakpoints
|
||||||
func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
||||||
if dbp.exited {
|
if procgrp.numValid() == 0 {
|
||||||
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, th := range dbp.threads {
|
for _, dbp := range procgrp.procs {
|
||||||
th.os.setbp = false
|
if dbp.exited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, th := range dbp.threads {
|
||||||
|
th.os.setbp = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trapthread.os.setbp = true
|
trapthread.os.setbp = true
|
||||||
|
|
||||||
// check if any other thread simultaneously received a SIGTRAP
|
// check if any other thread simultaneously received a SIGTRAP
|
||||||
for {
|
for {
|
||||||
th, err := dbp.trapWaitInternal(-1, trapWaitNohang)
|
th, err := trapWaitInternal(procgrp, -1, trapWaitNohang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, dbp.exitGuard(err)
|
p := procgrp.procForThread(th.ID)
|
||||||
|
return nil, exitGuard(p, procgrp, err)
|
||||||
}
|
}
|
||||||
if th == nil {
|
if th == nil {
|
||||||
break
|
break
|
||||||
@ -560,10 +636,20 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stop all threads that are still running
|
// stop all threads that are still running
|
||||||
for _, th := range dbp.threads {
|
for _, dbp := range procgrp.procs {
|
||||||
if th.os.running {
|
if dbp.exited {
|
||||||
if err := th.stop(); err != nil {
|
continue
|
||||||
return nil, dbp.exitGuard(err)
|
}
|
||||||
|
for _, th := range dbp.threads {
|
||||||
|
if th.os.running {
|
||||||
|
if err := th.stop(); err != nil {
|
||||||
|
if err == sys.ESRCH {
|
||||||
|
// thread exited
|
||||||
|
delete(dbp.threads, th.ID)
|
||||||
|
} else {
|
||||||
|
return nil, exitGuard(dbp, procgrp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -571,26 +657,60 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ
|
|||||||
// wait for all threads to stop
|
// wait for all threads to stop
|
||||||
for {
|
for {
|
||||||
allstopped := true
|
allstopped := true
|
||||||
for _, th := range dbp.threads {
|
for _, dbp := range procgrp.procs {
|
||||||
if th.os.running {
|
if dbp.exited {
|
||||||
allstopped = false
|
continue
|
||||||
break
|
}
|
||||||
|
for _, th := range dbp.threads {
|
||||||
|
if th.os.running {
|
||||||
|
allstopped = false
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allstopped {
|
if allstopped {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := dbp.trapWaitInternal(-1, trapWaitHalt)
|
_, err := trapWaitInternal(procgrp, -1, trapWaitHalt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
|
switchTrapthread := false
|
||||||
return nil, err
|
|
||||||
|
for _, dbp := range procgrp.procs {
|
||||||
|
if dbp.exited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := stop1(cctx, dbp, trapthread, &switchTrapthread)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switchTrapthread := false
|
if switchTrapthread {
|
||||||
|
trapthreadID := trapthread.ID
|
||||||
|
trapthread = nil
|
||||||
|
for _, dbp := range procgrp.procs {
|
||||||
|
if dbp.exited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, th := range dbp.threads {
|
||||||
|
if th.os.setbp && th.ThreadID() != trapthreadID {
|
||||||
|
return th, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trapthread, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativeThread, switchTrapthread *bool) error {
|
||||||
|
if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// set breakpoints on SIGTRAP threads
|
// set breakpoints on SIGTRAP threads
|
||||||
var err1 error
|
var err1 error
|
||||||
@ -649,27 +769,13 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ
|
|||||||
// Will switch to a different thread for trapthread because we don't
|
// Will switch to a different thread for trapthread because we don't
|
||||||
// want pkg/proc to believe that this thread was stopped by a
|
// want pkg/proc to believe that this thread was stopped by a
|
||||||
// hardcoded breakpoint.
|
// hardcoded breakpoint.
|
||||||
switchTrapthread = true
|
*switchTrapthread = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err1 != nil {
|
return err1
|
||||||
return nil, err1
|
|
||||||
}
|
|
||||||
|
|
||||||
if switchTrapthread {
|
|
||||||
trapthreadID := trapthread.ID
|
|
||||||
trapthread = nil
|
|
||||||
for _, th := range dbp.threads {
|
|
||||||
if th.os.setbp && th.ThreadID() != trapthreadID {
|
|
||||||
return th, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return trapthread, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) detach(kill bool) error {
|
func (dbp *nativeProcess) detach(kill bool) error {
|
||||||
@ -788,6 +894,25 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
|
|||||||
return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
|
return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FollowExec enables (or disables) follow exec mode
|
||||||
|
func (dbp *nativeProcess) FollowExec(v bool) error {
|
||||||
|
dbp.followExec = v
|
||||||
|
ptraceOptions := ptraceOptionsNormal
|
||||||
|
if dbp.followExec {
|
||||||
|
ptraceOptions = ptraceOptionsFollowExec
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
dbp.execPtraceFunc(func() {
|
||||||
|
for tid := range dbp.threads {
|
||||||
|
err = syscall.PtraceSetOptions(tid, ptraceOptions)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func killProcess(pid int) error {
|
func killProcess(pid int) error {
|
||||||
return sys.Kill(pid, sys.SIGINT)
|
return sys.Kill(pid, sys.SIGINT)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ type osProcessDetails struct {
|
|||||||
func (os *osProcessDetails) Close() {}
|
func (os *osProcessDetails) Close() {}
|
||||||
|
|
||||||
// Launch creates and begins debugging a new process.
|
// Launch creates and begins debugging a new process.
|
||||||
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.Target, error) {
|
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.TargetGroup, error) {
|
||||||
argv0Go := cmd[0]
|
argv0Go := cmd[0]
|
||||||
|
|
||||||
env := proc.DisableAsyncPreemptEnv()
|
env := proc.DisableAsyncPreemptEnv()
|
||||||
@ -140,7 +140,7 @@ func findExePath(pid int) (string, error) {
|
|||||||
var debugPrivilegeRequested = false
|
var debugPrivilegeRequested = false
|
||||||
|
|
||||||
// Attach to an existing process with the given PID.
|
// Attach to an existing process with the given PID.
|
||||||
func Attach(pid int, _ []string) (*proc.Target, error) {
|
func Attach(pid int, _ []string) (*proc.TargetGroup, error) {
|
||||||
var aperr error
|
var aperr error
|
||||||
if !debugPrivilegeRequested {
|
if !debugPrivilegeRequested {
|
||||||
debugPrivilegeRequested = true
|
debugPrivilegeRequested = true
|
||||||
@ -427,7 +427,8 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
|
||||||
|
dbp := procgrp.procs[0]
|
||||||
var err error
|
var err error
|
||||||
var tid, exitCode int
|
var tid, exitCode int
|
||||||
dbp.execPtraceFunc(func() {
|
dbp.execPtraceFunc(func() {
|
||||||
@ -474,7 +475,8 @@ func (dbp *nativeProcess) resume() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stop stops all running threads threads and sets breakpoints
|
// stop stops all running threads threads and sets breakpoints
|
||||||
func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
|
||||||
|
dbp := procgrp.procs[0]
|
||||||
if dbp.exited {
|
if dbp.exited {
|
||||||
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
return nil, proc.ErrProcessExited{Pid: dbp.pid}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ type osSpecificDetails struct {
|
|||||||
func (t *nativeThread) stop() (err error) {
|
func (t *nativeThread) stop() (err error) {
|
||||||
err = sys.Tgkill(t.dbp.pid, t.ID, sys.SIGSTOP)
|
err = sys.Tgkill(t.dbp.pid, t.ID, sys.SIGSTOP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == sys.ESRCH {
|
||||||
|
return
|
||||||
|
}
|
||||||
err = fmt.Errorf("stop err %s on thread %d", err, t.ID)
|
err = fmt.Errorf("stop err %s on thread %d", err, t.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -36,8 +36,7 @@ func TestIssue419(t *testing.T) {
|
|||||||
errChan := make(chan error, 2)
|
errChan := make(chan error, 2)
|
||||||
|
|
||||||
// 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, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
grp := proc.NewGroup(p)
|
|
||||||
setFunctionBreakpoint(p, t, "main.main")
|
setFunctionBreakpoint(p, t, "main.main")
|
||||||
assertNoError(grp.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
resumeChan := make(chan struct{}, 1)
|
resumeChan := make(chan struct{}, 1)
|
||||||
|
@ -23,8 +23,8 @@ func TestScopeWithEscapedVariable(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
withTestProcess("scopeescapevareval", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("scopeescapevareval", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
|
|
||||||
// On the breakpoint there are two 'a' variables in scope, the one that
|
// On the breakpoint there are two 'a' variables in scope, the one that
|
||||||
// isn't shadowed is a variable that escapes to the heap and figures in
|
// isn't shadowed is a variable that escapes to the heap and figures in
|
||||||
@ -72,7 +72,7 @@ func TestScope(t *testing.T) {
|
|||||||
|
|
||||||
scopeChecks := getScopeChecks(scopetestPath, t)
|
scopeChecks := getScopeChecks(scopetestPath, t)
|
||||||
|
|
||||||
withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("scopetest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
for i := range scopeChecks {
|
for i := range scopeChecks {
|
||||||
setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line)
|
setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line)
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ func TestScope(t *testing.T) {
|
|||||||
t.Logf("%d breakpoints set", len(scopeChecks))
|
t.Logf("%d breakpoints set", len(scopeChecks))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if err := p.Continue(); err != nil {
|
if err := grp.Continue(); err != nil {
|
||||||
if _, exited := err.(proc.ErrProcessExited); exited {
|
if _, exited := err.(proc.ErrProcessExited); exited {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,8 @@ const (
|
|||||||
type Target struct {
|
type Target struct {
|
||||||
Process
|
Process
|
||||||
|
|
||||||
proc ProcessInternal
|
proc ProcessInternal
|
||||||
recman RecordingManipulationInternal
|
recman RecordingManipulationInternal
|
||||||
continueOnce ContinueOnceFunc
|
|
||||||
|
|
||||||
pid int
|
pid int
|
||||||
|
|
||||||
@ -49,9 +48,6 @@ type Target struct {
|
|||||||
// case only one will be reported.
|
// case only one will be reported.
|
||||||
StopReason StopReason
|
StopReason StopReason
|
||||||
|
|
||||||
// CanDump is true if core dumping is supported.
|
|
||||||
CanDump bool
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
@ -148,18 +144,6 @@ 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,
|
|
||||||
type NewTargetConfig struct {
|
|
||||||
Path string // path of the main executable
|
|
||||||
DebugInfoDirs []string // Directories to search for split debug info
|
|
||||||
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
|
|
||||||
StopReason StopReason // Initial stop reason
|
|
||||||
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)
|
||||||
// where asyncpreemptoff is set to 1.
|
// where asyncpreemptoff is set to 1.
|
||||||
func DisableAsyncPreemptEnv() []string {
|
func DisableAsyncPreemptEnv() []string {
|
||||||
@ -174,15 +158,15 @@ func DisableAsyncPreemptEnv() []string {
|
|||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTarget returns an initialized Target object.
|
// newTarget returns an initialized Target object.
|
||||||
// The p argument can optionally implement the RecordingManipulation interface.
|
// The p argument can optionally implement the RecordingManipulation interface.
|
||||||
func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetConfig) (*Target, error) {
|
func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thread, path string) (*Target, error) {
|
||||||
entryPoint, err := p.EntryPoint()
|
entryPoint, err := p.EntryPoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.BinInfo().LoadBinaryInfo(cfg.Path, entryPoint, cfg.DebugInfoDirs)
|
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, grp.cfg.DebugInfoDirs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -196,11 +180,8 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
|
|||||||
Process: p,
|
Process: p,
|
||||||
proc: p,
|
proc: p,
|
||||||
fncallForG: make(map[int64]*callInjection),
|
fncallForG: make(map[int64]*callInjection),
|
||||||
StopReason: cfg.StopReason,
|
|
||||||
currentThread: currentThread,
|
currentThread: currentThread,
|
||||||
CanDump: cfg.CanDump,
|
|
||||||
pid: pid,
|
pid: pid,
|
||||||
continueOnce: cfg.ContinueOnce,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if recman, ok := p.(RecordingManipulationInternal); ok {
|
if recman, ok := p.(RecordingManipulationInternal); ok {
|
||||||
@ -219,7 +200,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
|
|||||||
t.gcache.init(p.BinInfo())
|
t.gcache.init(p.BinInfo())
|
||||||
t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
|
t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
|
||||||
|
|
||||||
if cfg.DisableAsyncPreempt {
|
if grp.cfg.DisableAsyncPreempt {
|
||||||
setAsyncPreemptOff(t, 1)
|
setAsyncPreemptOff(t, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,48 +48,55 @@ func (grp *TargetGroup) Next() (err error) {
|
|||||||
// processes. 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 (grp *TargetGroup) Continue() error {
|
func (grp *TargetGroup) Continue() error {
|
||||||
if len(grp.targets) != 1 {
|
if grp.numValid() == 0 {
|
||||||
panic("multiple targets not implemented")
|
_, err := grp.targets[0].Valid()
|
||||||
}
|
|
||||||
dbp := grp.Selected
|
|
||||||
if _, err := dbp.Valid(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, thread := range dbp.ThreadList() {
|
for _, dbp := range grp.targets {
|
||||||
thread.Common().CallReturn = false
|
if isvalid, _ := dbp.Valid(); !isvalid {
|
||||||
thread.Common().returnValues = nil
|
continue
|
||||||
|
}
|
||||||
|
for _, thread := range dbp.ThreadList() {
|
||||||
|
thread.Common().CallReturn = false
|
||||||
|
thread.Common().returnValues = nil
|
||||||
|
}
|
||||||
|
dbp.Breakpoints().WatchOutOfScope = nil
|
||||||
|
dbp.clearHardcodedBreakpoints()
|
||||||
}
|
}
|
||||||
dbp.Breakpoints().WatchOutOfScope = nil
|
|
||||||
dbp.clearHardcodedBreakpoints()
|
|
||||||
grp.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 grp.cctx.CheckAndClearManualStopRequest() {
|
if grp.cctx.CheckAndClearManualStopRequest() {
|
||||||
dbp.StopReason = StopManual
|
grp.finishManualStop()
|
||||||
dbp.clearHardcodedBreakpoints()
|
|
||||||
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
|
||||||
dbp.ClearSteppingBreakpoints()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
if grp.cctx.CheckAndClearManualStopRequest() {
|
if grp.cctx.CheckAndClearManualStopRequest() {
|
||||||
dbp.StopReason = StopManual
|
grp.finishManualStop()
|
||||||
dbp.clearHardcodedBreakpoints()
|
|
||||||
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
|
||||||
dbp.ClearSteppingBreakpoints()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dbp.ClearCaches()
|
for _, dbp := range grp.targets {
|
||||||
trapthread, stopReason, contOnceErr := grp.continueOnce([]ProcessInternal{grp.targets[0].proc}, grp.cctx)
|
dbp.ClearCaches()
|
||||||
dbp.StopReason = stopReason
|
}
|
||||||
|
trapthread, stopReason, contOnceErr := grp.procgrp.ContinueOnce(grp.cctx)
|
||||||
|
var traptgt *Target
|
||||||
|
if trapthread != nil {
|
||||||
|
traptgt = grp.TargetForThread(trapthread.ThreadID())
|
||||||
|
if traptgt == nil {
|
||||||
|
return fmt.Errorf("could not find target for thread %d", trapthread.ThreadID())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traptgt = grp.targets[0]
|
||||||
|
}
|
||||||
|
traptgt.StopReason = stopReason
|
||||||
|
|
||||||
threads := dbp.ThreadList()
|
it := ValidTargets{Group: grp}
|
||||||
for _, thread := range threads {
|
for it.Next() {
|
||||||
if thread.Breakpoint().Breakpoint != nil {
|
for _, thread := range it.ThreadList() {
|
||||||
thread.Breakpoint().Breakpoint.checkCondition(dbp, thread, thread.Breakpoint())
|
if thread.Breakpoint().Breakpoint != nil {
|
||||||
|
thread.Breakpoint().Breakpoint.checkCondition(it.Target, thread, thread.Breakpoint())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,31 +105,56 @@ func (grp *TargetGroup) Continue() error {
|
|||||||
// Issue #2078.
|
// Issue #2078.
|
||||||
// Errors are ignored because depending on why ContinueOnce failed this
|
// Errors are ignored because depending on why ContinueOnce failed this
|
||||||
// might very well not work.
|
// might very well not work.
|
||||||
if valid, _ := dbp.Valid(); valid {
|
_ = grp.setCurrentThreads(traptgt, trapthread)
|
||||||
if trapthread != nil {
|
|
||||||
_ = dbp.SwitchThread(trapthread.ThreadID())
|
|
||||||
} else if curth := dbp.CurrentThread(); curth != nil {
|
|
||||||
dbp.selectedGoroutine, _ = GetG(curth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pe, ok := contOnceErr.(ErrProcessExited); ok {
|
if pe, ok := contOnceErr.(ErrProcessExited); ok {
|
||||||
dbp.exitStatus = pe.Status
|
traptgt.exitStatus = pe.Status
|
||||||
}
|
}
|
||||||
return contOnceErr
|
return contOnceErr
|
||||||
}
|
}
|
||||||
if dbp.StopReason == StopLaunched {
|
if stopReason == StopLaunched {
|
||||||
dbp.ClearSteppingBreakpoints()
|
it.Reset()
|
||||||
|
for it.Next() {
|
||||||
|
it.Target.ClearSteppingBreakpoints()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callInjectionDone, callErr := callInjectionProtocol(dbp, threads)
|
var callInjectionDone bool
|
||||||
hcbpErr := dbp.handleHardcodedBreakpoints(trapthread, threads)
|
var callErr error
|
||||||
|
var hcbpErr error
|
||||||
|
it.Reset()
|
||||||
|
for it.Next() {
|
||||||
|
dbp := it.Target
|
||||||
|
threads := dbp.ThreadList()
|
||||||
|
callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads)
|
||||||
|
callInjectionDone = callInjectionDone || callInjectionDoneThis
|
||||||
|
if callInjectionDoneThis {
|
||||||
|
dbp.StopReason = StopCallReturned
|
||||||
|
}
|
||||||
|
if callErrThis != nil && callErr == nil {
|
||||||
|
callErr = callErrThis
|
||||||
|
}
|
||||||
|
hcbpErrThis := dbp.handleHardcodedBreakpoints(trapthread, threads)
|
||||||
|
if hcbpErrThis != nil && hcbpErr == nil {
|
||||||
|
hcbpErr = hcbpErrThis
|
||||||
|
}
|
||||||
|
}
|
||||||
// callErr and hcbpErr check delayed until after pickCurrentThread, which
|
// callErr and hcbpErr check delayed until after pickCurrentThread, which
|
||||||
// must always happen, otherwise the debugger could be left in an
|
// must always happen, otherwise the debugger could be left in an
|
||||||
// inconsistent state.
|
// inconsistent state.
|
||||||
|
|
||||||
if err := pickCurrentThread(dbp, trapthread, threads); err != nil {
|
it = ValidTargets{Group: grp}
|
||||||
return err
|
for it.Next() {
|
||||||
|
var th Thread = nil
|
||||||
|
if it.Target == traptgt {
|
||||||
|
th = trapthread
|
||||||
|
}
|
||||||
|
err := pickCurrentThread(it.Target, th)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
grp.pickCurrentTarget(traptgt)
|
||||||
|
dbp := grp.Selected
|
||||||
|
|
||||||
if callErr != nil {
|
if callErr != nil {
|
||||||
return callErr
|
return callErr
|
||||||
@ -138,7 +170,7 @@ func (grp *TargetGroup) Continue() error {
|
|||||||
case curbp.Active && curbp.Stepping:
|
case curbp.Active && curbp.Stepping:
|
||||||
if curbp.SteppingInto {
|
if curbp.SteppingInto {
|
||||||
// See description of proc.(*Process).next for the meaning of StepBreakpoints
|
// See description of proc.(*Process).next for the meaning of StepBreakpoints
|
||||||
if err := conditionErrors(threads); err != nil {
|
if err := conditionErrors(grp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if grp.GetDirection() == Forward {
|
if grp.GetDirection() == Forward {
|
||||||
@ -168,7 +200,7 @@ func (grp *TargetGroup) Continue() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dbp.StopReason = StopNextFinished
|
dbp.StopReason = StopNextFinished
|
||||||
return conditionErrors(threads)
|
return conditionErrors(grp)
|
||||||
}
|
}
|
||||||
case curbp.Active:
|
case curbp.Active:
|
||||||
onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints())
|
onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints())
|
||||||
@ -191,19 +223,58 @@ func (grp *TargetGroup) Continue() error {
|
|||||||
if curbp.Breakpoint.WatchType != 0 {
|
if curbp.Breakpoint.WatchType != 0 {
|
||||||
dbp.StopReason = StopWatchpoint
|
dbp.StopReason = StopWatchpoint
|
||||||
}
|
}
|
||||||
return conditionErrors(threads)
|
return conditionErrors(grp)
|
||||||
default:
|
default:
|
||||||
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
|
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
|
||||||
}
|
}
|
||||||
if callInjectionDone {
|
if callInjectionDone {
|
||||||
// a call injection was finished, don't let a breakpoint with a failed
|
// a call injection was finished, don't let a breakpoint with a failed
|
||||||
// condition or a step breakpoint shadow this.
|
// condition or a step breakpoint shadow this.
|
||||||
dbp.StopReason = StopCallReturned
|
return conditionErrors(grp)
|
||||||
return conditionErrors(threads)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (grp *TargetGroup) finishManualStop() {
|
||||||
|
for _, dbp := range grp.targets {
|
||||||
|
if isvalid, _ := dbp.Valid(); !isvalid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dbp.StopReason = StopManual
|
||||||
|
dbp.clearHardcodedBreakpoints()
|
||||||
|
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
|
||||||
|
dbp.ClearSteppingBreakpoints()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCurrentThreads switches traptgt to trapthread, then for each target in
|
||||||
|
// the group if its current thread exists it refreshes the current
|
||||||
|
// goroutine, otherwise it switches it to a randomly selected thread.
|
||||||
|
func (grp *TargetGroup) setCurrentThreads(traptgt *Target, trapthread Thread) error {
|
||||||
|
var err error
|
||||||
|
if traptgt != nil && trapthread != nil {
|
||||||
|
err = traptgt.SwitchThread(trapthread.ThreadID())
|
||||||
|
}
|
||||||
|
for _, tgt := range grp.targets {
|
||||||
|
if isvalid, _ := tgt.Valid(); !isvalid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := tgt.FindThread(tgt.currentThread.ThreadID()); ok {
|
||||||
|
tgt.selectedGoroutine, _ = GetG(tgt.currentThread)
|
||||||
|
} else {
|
||||||
|
threads := tgt.ThreadList()
|
||||||
|
if len(threads) > 0 {
|
||||||
|
err1 := tgt.SwitchThread(threads[0].ThreadID())
|
||||||
|
if err1 != nil && err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func isTraceOrTraceReturn(bp *Breakpoint) bool {
|
func isTraceOrTraceReturn(bp *Breakpoint) bool {
|
||||||
if bp.Logical == nil {
|
if bp.Logical == nil {
|
||||||
return false
|
return false
|
||||||
@ -211,14 +282,19 @@ func isTraceOrTraceReturn(bp *Breakpoint) bool {
|
|||||||
return bp.Logical.Tracepoint || bp.Logical.TraceReturn
|
return bp.Logical.Tracepoint || bp.Logical.TraceReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
func conditionErrors(threads []Thread) error {
|
func conditionErrors(grp *TargetGroup) error {
|
||||||
var condErr error
|
var condErr error
|
||||||
for _, th := range threads {
|
for _, dbp := range grp.targets {
|
||||||
if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil {
|
if isvalid, _ := dbp.Valid(); !isvalid {
|
||||||
if condErr == nil {
|
continue
|
||||||
condErr = bp.CondError
|
}
|
||||||
} else {
|
for _, th := range dbp.ThreadList() {
|
||||||
return fmt.Errorf("multiple errors evaluating conditions")
|
if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil {
|
||||||
|
if condErr == nil {
|
||||||
|
condErr = bp.CondError
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("multiple errors evaluating conditions")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,24 +302,88 @@ func conditionErrors(threads []Thread) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pick a new dbp.currentThread, with the following priority:
|
// pick a new dbp.currentThread, with the following priority:
|
||||||
|
//
|
||||||
// - a thread with an active stepping breakpoint
|
// - a thread with an active stepping breakpoint
|
||||||
// - a thread with an active breakpoint, prioritizing trapthread
|
// - a thread with an active breakpoint, prioritizing trapthread
|
||||||
// - trapthread
|
// - trapthread if it is not nil
|
||||||
func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
|
// - the previous current thread if it still exists
|
||||||
|
// - a randomly selected thread
|
||||||
|
func pickCurrentThread(dbp *Target, trapthread Thread) error {
|
||||||
|
threads := dbp.ThreadList()
|
||||||
for _, th := range threads {
|
for _, th := range threads {
|
||||||
if bp := th.Breakpoint(); bp.Active && bp.Stepping {
|
if bp := th.Breakpoint(); bp.Active && bp.Stepping {
|
||||||
return dbp.SwitchThread(th.ThreadID())
|
return dbp.SwitchThread(th.ThreadID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if bp := trapthread.Breakpoint(); bp.Active {
|
if trapthread != nil {
|
||||||
return dbp.SwitchThread(trapthread.ThreadID())
|
if bp := trapthread.Breakpoint(); bp.Active {
|
||||||
|
return dbp.SwitchThread(trapthread.ThreadID())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, th := range threads {
|
for _, th := range threads {
|
||||||
if bp := th.Breakpoint(); bp.Active {
|
if bp := th.Breakpoint(); bp.Active {
|
||||||
return dbp.SwitchThread(th.ThreadID())
|
return dbp.SwitchThread(th.ThreadID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dbp.SwitchThread(trapthread.ThreadID())
|
if trapthread != nil {
|
||||||
|
return dbp.SwitchThread(trapthread.ThreadID())
|
||||||
|
}
|
||||||
|
if _, ok := dbp.FindThread(dbp.currentThread.ThreadID()); ok {
|
||||||
|
dbp.selectedGoroutine, _ = GetG(dbp.currentThread)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(threads) > 0 {
|
||||||
|
return dbp.SwitchThread(threads[0].ThreadID())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pickCurrentTarget picks a new current target, with the following property:
|
||||||
|
//
|
||||||
|
// - a target with an active stepping breakpoint
|
||||||
|
// - a target with StopReason == StopCallReturned
|
||||||
|
// - a target with an active breakpoint, prioritizing traptgt
|
||||||
|
// - traptgt
|
||||||
|
func (grp *TargetGroup) pickCurrentTarget(traptgt *Target) {
|
||||||
|
if len(grp.targets) == 1 {
|
||||||
|
grp.Selected = grp.targets[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, dbp := range grp.targets {
|
||||||
|
if isvalid, _ := dbp.Valid(); !isvalid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bp := dbp.currentThread.Breakpoint()
|
||||||
|
if bp.Active && bp.Stepping {
|
||||||
|
grp.Selected = dbp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, dbp := range grp.targets {
|
||||||
|
if isvalid, _ := dbp.Valid(); !isvalid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dbp.StopReason == StopCallReturned {
|
||||||
|
grp.Selected = dbp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if traptgt.currentThread.Breakpoint().Active {
|
||||||
|
grp.Selected = traptgt
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, dbp := range grp.targets {
|
||||||
|
if isvalid, _ := dbp.Valid(); !isvalid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bp := dbp.currentThread.Breakpoint()
|
||||||
|
if bp.Active {
|
||||||
|
grp.Selected = dbp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grp.Selected = traptgt
|
||||||
}
|
}
|
||||||
|
|
||||||
func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) {
|
func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) {
|
||||||
|
@ -2,8 +2,11 @@ package proc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TargetGroup represents a group of target processes being debugged that
|
// TargetGroup represents a group of target processes being debugged that
|
||||||
@ -12,66 +15,114 @@ import (
|
|||||||
// enabled and the backend supports it, otherwise the group will always
|
// enabled and the backend supports it, otherwise the group will always
|
||||||
// contain a single target process.
|
// contain a single target process.
|
||||||
type TargetGroup struct {
|
type TargetGroup struct {
|
||||||
targets []*Target
|
procgrp ProcessGroup
|
||||||
Selected *Target
|
|
||||||
|
targets []*Target
|
||||||
|
Selected *Target
|
||||||
|
followExecEnabled bool
|
||||||
|
|
||||||
RecordingManipulation
|
RecordingManipulation
|
||||||
recman RecordingManipulationInternal
|
recman RecordingManipulationInternal
|
||||||
|
|
||||||
|
// StopReason describes the reason why the selected target process is stopped.
|
||||||
|
// A process could be stopped for multiple simultaneous reasons, in which
|
||||||
|
// case only one will be reported.
|
||||||
|
StopReason StopReason
|
||||||
|
|
||||||
// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
|
// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
|
||||||
// will keep the stepping breakpoints instead of clearing them.
|
// will keep the stepping breakpoints instead of clearing them.
|
||||||
KeepSteppingBreakpoints KeepSteppingBreakpoints
|
KeepSteppingBreakpoints KeepSteppingBreakpoints
|
||||||
|
|
||||||
LogicalBreakpoints map[int]*LogicalBreakpoint
|
LogicalBreakpoints map[int]*LogicalBreakpoint
|
||||||
|
|
||||||
continueOnce ContinueOnceFunc
|
cctx *ContinueOnceContext
|
||||||
cctx *ContinueOnceContext
|
cfg NewTargetGroupConfig
|
||||||
|
CanDump bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTargetGroupConfig contains the configuration for a new TargetGroup object,
|
||||||
|
type NewTargetGroupConfig struct {
|
||||||
|
DebugInfoDirs []string // Directories to search for split debug info
|
||||||
|
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
|
||||||
|
StopReason StopReason // Initial stop reason
|
||||||
|
CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason) (*Target, error)
|
||||||
|
|
||||||
// NewGroup creates a TargetGroup containing the specified Target.
|
// NewGroup creates a TargetGroup containing the specified Target.
|
||||||
func NewGroup(t *Target) *TargetGroup {
|
func NewGroup(procgrp ProcessGroup, cfg NewTargetGroupConfig) (*TargetGroup, AddTargetFunc) {
|
||||||
if t.partOfGroup {
|
grp := &TargetGroup{
|
||||||
panic("internal error: target is already part of a group")
|
procgrp: procgrp,
|
||||||
}
|
cctx: &ContinueOnceContext{},
|
||||||
t.partOfGroup = true
|
LogicalBreakpoints: make(map[int]*LogicalBreakpoint),
|
||||||
if t.Breakpoints().Logical == nil {
|
StopReason: cfg.StopReason,
|
||||||
t.Breakpoints().Logical = make(map[int]*LogicalBreakpoint)
|
cfg: cfg,
|
||||||
}
|
CanDump: cfg.CanDump,
|
||||||
return &TargetGroup{
|
|
||||||
RecordingManipulation: t.recman,
|
|
||||||
targets: []*Target{t},
|
|
||||||
Selected: t,
|
|
||||||
cctx: &ContinueOnceContext{},
|
|
||||||
recman: t.recman,
|
|
||||||
LogicalBreakpoints: t.Breakpoints().Logical,
|
|
||||||
continueOnce: t.continueOnce,
|
|
||||||
}
|
}
|
||||||
|
return grp, grp.addTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGroupRestart creates a new group of targets containing t and
|
// Restart copies breakpoints and follow exec status from oldgrp into grp.
|
||||||
// sets breakpoints and other attributes from oldgrp.
|
|
||||||
// Breakpoints that can not be set will be discarded, if discard is not nil
|
// Breakpoints that can not be set will be discarded, if discard is not nil
|
||||||
// it will be called for each discarded breakpoint.
|
// it will be called for each discarded breakpoint.
|
||||||
func NewGroupRestart(t *Target, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) *TargetGroup {
|
func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) {
|
||||||
grp := NewGroup(t)
|
for _, bp := range oldgrp.LogicalBreakpoints {
|
||||||
grp.LogicalBreakpoints = oldgrp.LogicalBreakpoints
|
if _, ok := grp.LogicalBreakpoints[bp.LogicalID]; ok {
|
||||||
t.Breakpoints().Logical = grp.LogicalBreakpoints
|
|
||||||
for _, bp := range grp.LogicalBreakpoints {
|
|
||||||
if bp.LogicalID < 0 || !bp.Enabled {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
grp.LogicalBreakpoints[bp.LogicalID] = bp
|
||||||
bp.TotalHitCount = 0
|
bp.TotalHitCount = 0
|
||||||
bp.HitCount = make(map[int64]uint64)
|
bp.HitCount = make(map[int64]uint64)
|
||||||
bp.Set.PidAddrs = nil // breakpoints set through a list of addresses can not be restored after a restart
|
bp.Set.PidAddrs = nil // breakpoints set through a list of addresses can not be restored after a restart
|
||||||
err := grp.EnableBreakpoint(bp)
|
if bp.Enabled {
|
||||||
if err != nil {
|
err := grp.EnableBreakpoint(bp)
|
||||||
if discard != nil {
|
if err != nil {
|
||||||
discard(bp, err)
|
if discard != nil {
|
||||||
|
discard(bp, err)
|
||||||
|
}
|
||||||
|
delete(grp.LogicalBreakpoints, bp.LogicalID)
|
||||||
}
|
}
|
||||||
delete(grp.LogicalBreakpoints, bp.LogicalID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return grp
|
if oldgrp.followExecEnabled {
|
||||||
|
grp.FollowExec(true, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason) (*Target, error) {
|
||||||
|
t, err := grp.newTarget(p, pid, currentThread, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.StopReason = stopReason
|
||||||
|
//TODO(aarzilli): check if the target's command line matches the regex
|
||||||
|
if t.partOfGroup {
|
||||||
|
panic("internal error: target is already part of group")
|
||||||
|
}
|
||||||
|
t.partOfGroup = true
|
||||||
|
if grp.RecordingManipulation == nil {
|
||||||
|
grp.RecordingManipulation = t.recman
|
||||||
|
grp.recman = t.recman
|
||||||
|
}
|
||||||
|
if grp.Selected == nil {
|
||||||
|
grp.Selected = t
|
||||||
|
}
|
||||||
|
t.Breakpoints().Logical = grp.LogicalBreakpoints
|
||||||
|
logger := logflags.DebuggerLogger()
|
||||||
|
for _, lbp := range grp.LogicalBreakpoints {
|
||||||
|
if lbp.LogicalID < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := enableBreakpointOnTarget(t, lbp)
|
||||||
|
if err != nil {
|
||||||
|
logger.Debugf("could not enable breakpoint %d on new target %d: %v", lbp.LogicalID, t.Pid(), err)
|
||||||
|
} else {
|
||||||
|
logger.Debugf("breakpoint %d enabled on new target %d: %v", lbp.LogicalID, t.Pid(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grp.targets = append(grp.targets, t)
|
||||||
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Targets returns a slice of all targets in the group, including the
|
// Targets returns a slice of all targets in the group, including the
|
||||||
@ -95,10 +146,22 @@ func (grp *TargetGroup) Valid() (bool, error) {
|
|||||||
return false, err0
|
return false, err0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (grp *TargetGroup) numValid() int {
|
||||||
|
r := 0
|
||||||
|
for _, t := range grp.targets {
|
||||||
|
ok, _ := t.Valid()
|
||||||
|
if ok {
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// Detach detaches all targets in the group.
|
// Detach detaches all targets in the group.
|
||||||
func (grp *TargetGroup) Detach(kill bool) error {
|
func (grp *TargetGroup) Detach(kill bool) error {
|
||||||
var errs []string
|
var errs []string
|
||||||
for _, t := range grp.targets {
|
for i := len(grp.targets) - 1; i >= 0; i-- {
|
||||||
|
t := grp.targets[i]
|
||||||
isvalid, _ := t.Valid()
|
isvalid, _ := t.Valid()
|
||||||
if !isvalid {
|
if !isvalid {
|
||||||
continue
|
continue
|
||||||
@ -144,12 +207,10 @@ func (grp *TargetGroup) ThreadList() []Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TargetForThread returns the target containing the given thread.
|
// TargetForThread returns the target containing the given thread.
|
||||||
func (grp *TargetGroup) TargetForThread(thread Thread) *Target {
|
func (grp *TargetGroup) TargetForThread(tid int) *Target {
|
||||||
for _, t := range grp.targets {
|
for _, t := range grp.targets {
|
||||||
for _, th := range t.ThreadList() {
|
if _, ok := t.FindThread(tid); ok {
|
||||||
if th == thread {
|
return t
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -274,6 +335,26 @@ func (grp *TargetGroup) DisableBreakpoint(lbp *LogicalBreakpoint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FollowExec enables or disables follow exec mode. When follow exec mode is
|
||||||
|
// enabled new processes spawned by the target process are automatically
|
||||||
|
// added to the target group.
|
||||||
|
// If regex is not the empty string only processes whose command line
|
||||||
|
// matches regex will be added to the target group.
|
||||||
|
func (grp *TargetGroup) FollowExec(v bool, regex string) error {
|
||||||
|
if regex != "" {
|
||||||
|
return errors.New("regex not implemented")
|
||||||
|
}
|
||||||
|
it := ValidTargets{Group: grp}
|
||||||
|
for it.Next() {
|
||||||
|
err := it.proc.FollowExec(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grp.followExecEnabled = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ValidTargets iterates through all valid targets in Group.
|
// ValidTargets iterates through all valid targets in Group.
|
||||||
type ValidTargets struct {
|
type ValidTargets struct {
|
||||||
*Target
|
*Target
|
||||||
@ -295,3 +376,9 @@ func (it *ValidTargets) Next() bool {
|
|||||||
it.Target = nil
|
it.Target = nil
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset returns the iterator to the start of the group.
|
||||||
|
func (it *ValidTargets) Reset() {
|
||||||
|
it.Target = nil
|
||||||
|
it.start = 0
|
||||||
|
}
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
|
|
||||||
func TestGoroutineCreationLocation(t *testing.T) {
|
func TestGoroutineCreationLocation(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
bp := setFunctionBreakpoint(p, t, "main.agoroutine")
|
bp := setFunctionBreakpoint(p, t, "main.agoroutine")
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
|
|
||||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||||
assertNoError(err, t, "GoroutinesInfo")
|
assertNoError(err, t, "GoroutinesInfo")
|
||||||
@ -39,6 +39,6 @@ func TestGoroutineCreationLocation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.ClearBreakpoint(bp.Addr)
|
p.ClearBreakpoint(bp.Addr)
|
||||||
p.Continue()
|
grp.Continue()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -140,8 +140,8 @@ func TestVariableEvaluation2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
assertNoError(err, t, "Continue() returned an error")
|
assertNoError(err, t, "Continue() returned an error")
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
@ -198,8 +198,8 @@ func TestSetVariable(t *testing.T) {
|
|||||||
{"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"},
|
{"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"},
|
||||||
}
|
}
|
||||||
|
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
if tc.name == "iface1" && tc.expr == "parr" {
|
if tc.name == "iface1" && tc.expr == "parr" {
|
||||||
@ -267,8 +267,8 @@ func TestVariableEvaluationShort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
assertNoError(err, t, "Continue() returned an error")
|
assertNoError(err, t, "Continue() returned an error")
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
@ -323,8 +323,8 @@ func TestMultilineVariableEvaluation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
assertNoError(err, t, "Continue() returned an error")
|
assertNoError(err, t, "Continue() returned an error")
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
@ -399,8 +399,8 @@ func TestLocalVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
assertNoError(err, t, "Continue() returned an error")
|
assertNoError(err, t, "Continue() returned an error")
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
@ -436,7 +436,7 @@ func TestLocalVariables(t *testing.T) {
|
|||||||
|
|
||||||
func TestEmbeddedStruct(t *testing.T) {
|
func TestEmbeddedStruct(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
testcases := []varTest{
|
testcases := []varTest{
|
||||||
{"b.val", true, "-314", "-314", "int", nil},
|
{"b.val", true, "-314", "-314", "int", nil},
|
||||||
{"b.A.val", true, "-314", "-314", "int", nil},
|
{"b.A.val", true, "-314", "-314", "int", nil},
|
||||||
@ -460,7 +460,7 @@ func TestEmbeddedStruct(t *testing.T) {
|
|||||||
{"w4.F", false, ``, ``, "", errors.New("w4 has no member F")},
|
{"w4.F", false, ``, ``, "", errors.New("w4 has no member F")},
|
||||||
{"w5.F", false, ``, ``, "", errors.New("w5 has no member F")},
|
{"w5.F", false, ``, ``, "", errors.New("w5 has no member F")},
|
||||||
}
|
}
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
|
|
||||||
ver, _ := goversion.Parse(runtime.Version())
|
ver, _ := goversion.Parse(runtime.Version())
|
||||||
if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
|
if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
|
||||||
@ -491,8 +491,8 @@ func TestEmbeddedStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestComplexSetting(t *testing.T) {
|
func TestComplexSetting(t *testing.T) {
|
||||||
withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
err := p.Continue()
|
err := grp.Continue()
|
||||||
assertNoError(err, t, "Continue() returned an error")
|
assertNoError(err, t, "Continue() returned an error")
|
||||||
|
|
||||||
h := func(setExpr, value string) {
|
h := func(setExpr, value string) {
|
||||||
@ -842,8 +842,8 @@ func TestEvalExpression(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
for i, tc := range testcases {
|
for i, tc := range testcases {
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
||||||
@ -872,8 +872,8 @@ func TestEvalExpression(t *testing.T) {
|
|||||||
|
|
||||||
func TestEvalAddrAndCast(t *testing.T) {
|
func TestEvalAddrAndCast(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
c1addr, err := evalVariableWithCfg(p, "&c1", pnormalLoadConfig)
|
c1addr, err := evalVariableWithCfg(p, "&c1", pnormalLoadConfig)
|
||||||
assertNoError(err, t, "EvalExpression(&c1)")
|
assertNoError(err, t, "EvalExpression(&c1)")
|
||||||
c1addrstr := api.ConvertVar(c1addr).SinglelineString()
|
c1addrstr := api.ConvertVar(c1addr).SinglelineString()
|
||||||
@ -899,8 +899,8 @@ func TestEvalAddrAndCast(t *testing.T) {
|
|||||||
|
|
||||||
func TestMapEvaluation(t *testing.T) {
|
func TestMapEvaluation(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
m1v, err := evalVariableWithCfg(p, "m1", pnormalLoadConfig)
|
m1v, err := evalVariableWithCfg(p, "m1", pnormalLoadConfig)
|
||||||
assertNoError(err, t, "EvalVariable()")
|
assertNoError(err, t, "EvalVariable()")
|
||||||
m1 := api.ConvertVar(m1v)
|
m1 := api.ConvertVar(m1v)
|
||||||
@ -941,8 +941,8 @@ func TestMapEvaluation(t *testing.T) {
|
|||||||
|
|
||||||
func TestUnsafePointer(t *testing.T) {
|
func TestUnsafePointer(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
up1v, err := evalVariableWithCfg(p, "up1", pnormalLoadConfig)
|
up1v, err := evalVariableWithCfg(p, "up1", pnormalLoadConfig)
|
||||||
assertNoError(err, t, "EvalVariable(up1)")
|
assertNoError(err, t, "EvalVariable(up1)")
|
||||||
up1 := api.ConvertVar(up1v)
|
up1 := api.ConvertVar(up1v)
|
||||||
@ -979,8 +979,8 @@ func TestIssue426(t *testing.T) {
|
|||||||
// Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces
|
// Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces
|
||||||
// differs from the serialization used by the linker to produce DWARF type information
|
// differs from the serialization used by the linker to produce DWARF type information
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
v, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig)
|
v, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig)
|
||||||
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name))
|
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name))
|
||||||
@ -1065,8 +1065,8 @@ func TestPackageRenames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
testPackageRenamesHelper(t, p, testcases)
|
testPackageRenamesHelper(t, p, testcases)
|
||||||
|
|
||||||
testPackageRenamesHelper(t, p, testcases1_9)
|
testPackageRenamesHelper(t, p, testcases1_9)
|
||||||
@ -1101,8 +1101,8 @@ func TestConstants(t *testing.T) {
|
|||||||
// Not supported on 1.9 or earlier
|
// Not supported on 1.9 or earlier
|
||||||
t.Skip("constants added in go 1.10")
|
t.Skip("constants added in go 1.10")
|
||||||
}
|
}
|
||||||
withTestProcess("consts", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("consts", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue")
|
assertNoError(grp.Continue(), t, "Continue")
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
variable, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig)
|
variable, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig)
|
||||||
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name))
|
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name))
|
||||||
@ -1112,9 +1112,9 @@ func TestConstants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue1075(t *testing.T) {
|
func TestIssue1075(t *testing.T) {
|
||||||
withTestProcess("clientdo", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("clientdo", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
setFunctionBreakpoint(p, t, "net/http.(*Client).Do")
|
setFunctionBreakpoint(p, t, "net/http.(*Client).Do")
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
scope, err := proc.GoroutineScope(p, p.CurrentThread())
|
scope, err := proc.GoroutineScope(p, p.CurrentThread())
|
||||||
assertNoError(err, t, fmt.Sprintf("GoroutineScope (%d)", i))
|
assertNoError(err, t, fmt.Sprintf("GoroutineScope (%d)", i))
|
||||||
@ -1260,9 +1260,8 @@ func TestCallFunction(t *testing.T) {
|
|||||||
{`floatsum(1, 2)`, []string{":float64:3"}, nil},
|
{`floatsum(1, 2)`, []string{":float64:3"}, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
grp := proc.NewGroup(p)
|
testCallFunctionSetBreakpoint(t, p, grp, fixture)
|
||||||
testCallFunctionSetBreakpoint(t, p, fixture)
|
|
||||||
|
|
||||||
assertNoError(grp.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
@ -1303,7 +1302,7 @@ func TestCallFunction(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, fixture protest.Fixture) {
|
func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
buf, err := ioutil.ReadFile(fixture.Source)
|
buf, err := ioutil.ReadFile(fixture.Source)
|
||||||
assertNoError(err, t, "ReadFile")
|
assertNoError(err, t, "ReadFile")
|
||||||
for i, line := range strings.Split(string(buf), "\n") {
|
for i, line := range strings.Split(string(buf), "\n") {
|
||||||
@ -1390,8 +1389,8 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te
|
|||||||
|
|
||||||
func TestIssue1531(t *testing.T) {
|
func TestIssue1531(t *testing.T) {
|
||||||
// Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0.
|
// Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0.
|
||||||
withTestProcess("issue1531", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("issue1531", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
|
|
||||||
hasKeys := func(mv *proc.Variable, keys ...string) {
|
hasKeys := func(mv *proc.Variable, keys ...string) {
|
||||||
n := 0
|
n := 0
|
||||||
@ -1453,9 +1452,9 @@ func assertCurrentLocationFunction(p *proc.Target, t *testing.T, fnname string)
|
|||||||
func TestPluginVariables(t *testing.T) {
|
func TestPluginVariables(t *testing.T) {
|
||||||
pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
|
pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
|
||||||
|
|
||||||
withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
setFileBreakpoint(p, t, fixture.Source, 41)
|
setFileBreakpoint(p, t, fixture.Source, 41)
|
||||||
assertNoError(p.Continue(), t, "Continue 1")
|
assertNoError(grp.Continue(), t, "Continue 1")
|
||||||
|
|
||||||
bp := setFunctionBreakpoint(p, t, "github.com/go-delve/delve/_fixtures/plugin2.TypesTest")
|
bp := setFunctionBreakpoint(p, t, "github.com/go-delve/delve/_fixtures/plugin2.TypesTest")
|
||||||
t.Logf("bp.Addr = %#x", bp.Addr)
|
t.Logf("bp.Addr = %#x", bp.Addr)
|
||||||
@ -1465,7 +1464,7 @@ func TestPluginVariables(t *testing.T) {
|
|||||||
t.Logf("%#x %s\n", image.StaticBase, image.Path)
|
t.Logf("%#x %s\n", image.StaticBase, image.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNoError(p.Continue(), t, "Continue 2")
|
assertNoError(grp.Continue(), t, "Continue 2")
|
||||||
|
|
||||||
// test that PackageVariables returns variables from the executable and plugins
|
// test that PackageVariables returns variables from the executable and plugins
|
||||||
scope, err := evalScope(p)
|
scope, err := evalScope(p)
|
||||||
@ -1495,15 +1494,15 @@ func TestPluginVariables(t *testing.T) {
|
|||||||
|
|
||||||
// test that the concrete type -> interface{} conversion works across plugins (mostly tests proc.dwarfToRuntimeType)
|
// test that the concrete type -> interface{} conversion works across plugins (mostly tests proc.dwarfToRuntimeType)
|
||||||
assertNoError(setVariable(p, "plugin2.A", "main.ExeGlobal"), t, "setVariable(plugin2.A = main.ExeGlobal)")
|
assertNoError(setVariable(p, "plugin2.A", "main.ExeGlobal"), t, "setVariable(plugin2.A = main.ExeGlobal)")
|
||||||
assertNoError(p.Continue(), t, "Continue 3")
|
assertNoError(grp.Continue(), t, "Continue 3")
|
||||||
assertCurrentLocationFunction(p, t, "github.com/go-delve/delve/_fixtures/plugin2.aIsNotNil")
|
assertCurrentLocationFunction(p, t, "github.com/go-delve/delve/_fixtures/plugin2.aIsNotNil")
|
||||||
vstr, err := evalVariableWithCfg(p, "str", pnormalLoadConfig)
|
vstr, err := evalVariableWithCfg(p, "str", pnormalLoadConfig)
|
||||||
assertNoError(err, t, "Eval(str)")
|
assertNoError(err, t, "Eval(str)")
|
||||||
assertVariable(t, vstr, varTest{"str", true, `"success"`, ``, `string`, nil})
|
assertVariable(t, vstr, varTest{"str", true, `"success"`, ``, `string`, nil})
|
||||||
|
|
||||||
assertNoError(p.StepOut(), t, "StepOut")
|
assertNoError(grp.StepOut(), t, "StepOut")
|
||||||
assertNoError(p.StepOut(), t, "StepOut")
|
assertNoError(grp.StepOut(), t, "StepOut")
|
||||||
assertNoError(p.Next(), t, "Next")
|
assertNoError(grp.Next(), t, "Next")
|
||||||
|
|
||||||
// read interface variable, inside executable code, with a concrete type defined in a plugin
|
// read interface variable, inside executable code, with a concrete type defined in a plugin
|
||||||
vb, err := evalVariableWithCfg(p, "b", pnormalLoadConfig)
|
vb, err := evalVariableWithCfg(p, "b", pnormalLoadConfig)
|
||||||
@ -1534,8 +1533,8 @@ func TestCgoEval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestProcess("testvariablescgo/", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariablescgo/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue() returned an error")
|
assertNoError(grp.Continue(), t, "Continue() returned an error")
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
||||||
if err != nil && err.Error() == "evaluating methods not supported on this version of Go" {
|
if err != nil && err.Error() == "evaluating methods not supported on this version of Go" {
|
||||||
@ -1581,9 +1580,9 @@ func TestEvalExpressionGenerics(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
withTestProcess("testvariables_generic", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testvariables_generic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
for i, tcs := range testcases {
|
for i, tcs := range testcases {
|
||||||
assertNoError(p.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i))
|
assertNoError(grp.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i))
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
|
||||||
if tc.err == nil {
|
if tc.err == nil {
|
||||||
@ -1604,8 +1603,8 @@ func TestEvalExpressionGenerics(t *testing.T) {
|
|||||||
|
|
||||||
// Test the behavior when reading dangling pointers produced by unsafe code.
|
// Test the behavior when reading dangling pointers produced by unsafe code.
|
||||||
func TestBadUnsafePtr(t *testing.T) {
|
func TestBadUnsafePtr(t *testing.T) {
|
||||||
withTestProcess("testunsafepointers", t, func(p *proc.Target, fixture protest.Fixture) {
|
withTestProcess("testunsafepointers", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(grp.Continue(), t, "Continue()")
|
||||||
|
|
||||||
// danglingPtrPtr is a pointer with value 0x42, which is an unreadable
|
// danglingPtrPtr is a pointer with value 0x42, which is an unreadable
|
||||||
// address.
|
// address.
|
||||||
|
@ -79,6 +79,7 @@ func (ft *FakeTerminal) ExecStarlark(starlarkProgram string) (outstr string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ft *FakeTerminal) MustExec(cmdstr string) string {
|
func (ft *FakeTerminal) MustExec(cmdstr string) string {
|
||||||
|
ft.t.Helper()
|
||||||
outstr, err := ft.Exec(cmdstr)
|
outstr, err := ft.Exec(cmdstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ft.t.Errorf("output of %q: %q", cmdstr, outstr)
|
ft.t.Errorf("output of %q: %q", cmdstr, outstr)
|
||||||
|
@ -161,30 +161,28 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
if len(d.processArgs) > 0 {
|
if len(d.processArgs) > 0 {
|
||||||
path = d.processArgs[0]
|
path = d.processArgs[0]
|
||||||
}
|
}
|
||||||
p, err := d.Attach(d.config.AttachPid, path)
|
var err error
|
||||||
|
d.target, err = d.Attach(d.config.AttachPid, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = go11DecodeErrorCheck(err)
|
err = go11DecodeErrorCheck(err)
|
||||||
err = noDebugErrorWarning(err)
|
err = noDebugErrorWarning(err)
|
||||||
return nil, attachErrorMessage(d.config.AttachPid, err)
|
return nil, attachErrorMessage(d.config.AttachPid, err)
|
||||||
}
|
}
|
||||||
d.target = proc.NewGroup(p)
|
|
||||||
|
|
||||||
case d.config.CoreFile != "":
|
case d.config.CoreFile != "":
|
||||||
var p *proc.Target
|
|
||||||
var err error
|
var err error
|
||||||
switch d.config.Backend {
|
switch d.config.Backend {
|
||||||
case "rr":
|
case "rr":
|
||||||
d.log.Infof("opening trace %s", d.config.CoreFile)
|
d.log.Infof("opening trace %s", d.config.CoreFile)
|
||||||
p, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories)
|
d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories)
|
||||||
default:
|
default:
|
||||||
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
|
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
|
||||||
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories)
|
d.target, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = go11DecodeErrorCheck(err)
|
err = go11DecodeErrorCheck(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
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
|
||||||
@ -192,7 +190,8 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
d.log.Infof("launching process with args: %v", d.processArgs)
|
d.log.Infof("launching process with args: %v", d.processArgs)
|
||||||
p, err := d.Launch(d.processArgs, d.config.WorkingDir)
|
var err error
|
||||||
|
d.target, err = d.Launch(d.processArgs, d.config.WorkingDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*proc.ErrUnsupportedArch); !ok {
|
if _, ok := err.(*proc.ErrUnsupportedArch); !ok {
|
||||||
err = go11DecodeErrorCheck(err)
|
err = go11DecodeErrorCheck(err)
|
||||||
@ -201,10 +200,6 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if p != nil {
|
|
||||||
// if p == nil and err == nil then we are doing a recording, don't touch d.target
|
|
||||||
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
|
||||||
@ -245,7 +240,7 @@ func (d *Debugger) TargetGoVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch will start a process with the given args and working directory.
|
// Launch will start a process with the given args and working directory.
|
||||||
func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) {
|
func (d *Debugger) Launch(processArgs []string, wd string) (*proc.TargetGroup, error) {
|
||||||
fullpath, err := verifyBinaryFormat(processArgs[0])
|
fullpath, err := verifyBinaryFormat(processArgs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -285,7 +280,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
|
|||||||
go func() {
|
go func() {
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
p, err := d.recordingRun(run)
|
grp, err := d.recordingRun(run)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.log.Errorf("could not record target: %v", err)
|
d.log.Errorf("could not record target: %v", err)
|
||||||
// this is ugly but we can't respond to any client requests at this
|
// this is ugly but we can't respond to any client requests at this
|
||||||
@ -293,7 +288,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
d.recordingDone()
|
d.recordingDone()
|
||||||
d.target = proc.NewGroup(p)
|
d.target = grp
|
||||||
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)
|
||||||
@ -332,7 +327,7 @@ func (d *Debugger) isRecording() bool {
|
|||||||
return d.stopRecording != nil
|
return d.stopRecording != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) recordingRun(run func() (string, error)) (*proc.Target, error) {
|
func (d *Debugger) recordingRun(run func() (string, error)) (*proc.TargetGroup, error) {
|
||||||
tracedir, err := run()
|
tracedir, err := run()
|
||||||
if err != nil && tracedir == "" {
|
if err != nil && tracedir == "" {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -342,7 +337,7 @@ func (d *Debugger) recordingRun(run func() (string, error)) (*proc.Target, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach will attach to the process specified by 'pid'.
|
// Attach will attach to the process specified by 'pid'.
|
||||||
func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) {
|
func (d *Debugger) Attach(pid int, path string) (*proc.TargetGroup, error) {
|
||||||
switch d.config.Backend {
|
switch d.config.Backend {
|
||||||
case "native":
|
case "native":
|
||||||
return native.Attach(pid, d.config.DebugInfoDirectories)
|
return native.Attach(pid, d.config.DebugInfoDirectories)
|
||||||
@ -360,7 +355,7 @@ func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) {
|
|||||||
|
|
||||||
var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install Xcode's command line tools or lldb-server")
|
var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install Xcode's command line tools or lldb-server")
|
||||||
|
|
||||||
func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error) {
|
func betterGdbserialLaunchError(p *proc.TargetGroup, err error) (*proc.TargetGroup, error) {
|
||||||
if runtime.GOOS != "darwin" {
|
if runtime.GOOS != "darwin" {
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
@ -482,7 +477,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
d.processArgs = append([]string{d.processArgs[0]}, newArgs...)
|
d.processArgs = append([]string{d.processArgs[0]}, newArgs...)
|
||||||
d.config.Redirects = newRedirects
|
d.config.Redirects = newRedirects
|
||||||
}
|
}
|
||||||
var p *proc.Target
|
var grp *proc.TargetGroup
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if rebuild {
|
if rebuild {
|
||||||
@ -510,19 +505,20 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.recordingStart(stop)
|
d.recordingStart(stop)
|
||||||
p, err = d.recordingRun(run)
|
grp, err = d.recordingRun(run)
|
||||||
d.recordingDone()
|
d.recordingDone()
|
||||||
} else {
|
} else {
|
||||||
p, err = d.Launch(d.processArgs, d.config.WorkingDir)
|
grp, err = d.Launch(d.processArgs, d.config.WorkingDir)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not launch process: %s", err)
|
return nil, fmt.Errorf("could not launch process: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
discarded := []api.DiscardedBreakpoint{}
|
discarded := []api.DiscardedBreakpoint{}
|
||||||
d.target = proc.NewGroupRestart(p, d.target, func(oldBp *proc.LogicalBreakpoint, err error) {
|
proc.Restart(grp, d.target, func(oldBp *proc.LogicalBreakpoint, err error) {
|
||||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: err.Error()})
|
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: err.Error()})
|
||||||
})
|
})
|
||||||
|
d.target = grp
|
||||||
return discarded, nil
|
return discarded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1321,7 +1317,7 @@ func (d *Debugger) collectBreakpointInformation(apiThread *api.Thread, thread pr
|
|||||||
bpi := &api.BreakpointInfo{}
|
bpi := &api.BreakpointInfo{}
|
||||||
apiThread.BreakpointInfo = bpi
|
apiThread.BreakpointInfo = bpi
|
||||||
|
|
||||||
tgt := d.target.TargetForThread(thread)
|
tgt := d.target.TargetForThread(thread.ThreadID())
|
||||||
|
|
||||||
if bp.Goroutine {
|
if bp.Goroutine {
|
||||||
g, err := proc.GetG(thread)
|
g, err := proc.GetG(thread)
|
||||||
@ -2141,7 +2137,7 @@ func (d *Debugger) DumpStart(dest string) error {
|
|||||||
|
|
||||||
//TODO(aarzilli): what do we do if the user switches to a different target after starting a dump but before it's finished?
|
//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 {
|
if !d.target.CanDump {
|
||||||
d.targetMutex.Unlock()
|
d.targetMutex.Unlock()
|
||||||
return ErrCoreDumpNotSupported
|
return ErrCoreDumpNotSupported
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user