diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 7ebf751c67..564850037d 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -216,7 +216,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e return nil, err } - return proc.NewTarget(p), nil + return proc.NewTarget(p, false), nil } // initialize for core files doesn't do much diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 13b1bee52b..f73d7691e8 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -406,7 +406,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string if err != nil { return nil, err } - return proc.NewTarget(p), nil + return proc.NewTarget(p, false), nil } // LLDBAttach starts an instance of lldb-server and connects to it, asking @@ -458,7 +458,7 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err if err != nil { return nil, err } - return proc.NewTarget(p), nil + return proc.NewTarget(p, false), nil } // EntryPoint will return the process entry point address, useful for diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index dc437fcd80..ff5afec9d6 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -96,7 +96,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) return nil, err } - return proc.NewTarget(p), nil + return proc.NewTarget(p, false), nil } // ErrPerfEventParanoid is the error returned by Reply and Record if diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index 61f82310d5..9e00df1afc 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -226,7 +226,8 @@ func TestCheckpoints(t *testing.T) { // Move back to checkpoint, check that the output of 'when' is the same as // what it was when we set the breakpoint p.Restart(fmt.Sprintf("c%d", cpid)) - p.SwitchGoroutine(1) + g, _ := proc.FindGoroutine(p, 1) + p.SwitchGoroutine(g) when2, loc2 := getPosition(p, t) t.Logf("when2: %q (%#x) %x", when2, loc2.PC, p.CurrentThread().ThreadID()) if loc2.PC != loc0.PC { @@ -253,7 +254,8 @@ func TestCheckpoints(t *testing.T) { _, err = p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint") p.Restart(fmt.Sprintf("c%d", cpid)) - p.SwitchGoroutine(1) + g, _ = proc.FindGoroutine(p, 1) + p.SwitchGoroutine(g) assertNoError(proc.Next(p), t, "First Next") assertNoError(proc.Next(p), t, "Second Next") when4, loc4 := getPosition(p, t) diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index cbaf91e2e1..41a1eacae9 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -126,7 +126,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, return nil, err } - return proc.NewTarget(dbp), err + return proc.NewTarget(dbp, false), err } // Attach to an existing process with the given PID. @@ -158,7 +158,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) { dbp.Detach(false) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } // Kill kills the process. diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index ad7d226aef..f24d600472 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -87,7 +87,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (* if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } // Attach to an existing process with the given PID. Once attached, if @@ -111,7 +111,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { dbp.Detach(false) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } func initialize(dbp *Process) error { diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index a45ec85500..855d399eaa 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -92,7 +92,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (* if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } // Attach to an existing process with the given PID. Once attached, if @@ -123,7 +123,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { if err != nil { return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } func initialize(dbp *Process) error { diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 5a763bccf3..6c36443a5b 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "syscall" "unsafe" @@ -55,6 +56,15 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, } closer.Close() + env := os.Environ() + for i := range env { + if strings.HasPrefix(env[i], "GODEBUG=") { + // Go 1.14 asynchronous preemption mechanism is incompatible with + // debuggers, see: https://github.com/golang/go/issues/36494 + env[i] += ",asyncpreemptoff=1" + } + } + var p *os.Process dbp := New(0) dbp.execPtraceFunc(func() { @@ -64,6 +74,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, Sys: &syscall.SysProcAttr{ CreationFlags: _DEBUG_ONLY_THIS_PROCESS, }, + Env: env, } p, err = os.StartProcess(argv0Go, cmd, attr) }) @@ -79,7 +90,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, dbp.Detach(true) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, true), nil } func initialize(dbp *Process) error { @@ -168,7 +179,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) { dbp.Detach(true) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, true), nil } // kill kills the process. @@ -472,6 +483,8 @@ func (dbp *Process) stop(trapthread *Thread) (err error) { func (dbp *Process) detach(kill bool) error { if !kill { + //TODO(aarzilli): when debug.Target exist Detach should be moved to + // debug.Target and the call to RestoreAsyncPreempt should be moved there. for _, thread := range dbp.threads { _, err := _ResumeThread(thread.os.hThread) if err != nil { diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index b425831049..9e37104be2 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -5,9 +5,12 @@ import ( "errors" "fmt" "go/ast" + "go/constant" "go/token" "path/filepath" "strconv" + + "github.com/go-delve/delve/pkg/goversion" ) // ErrNotExecutable is returned after attempting to execute a non-executable file @@ -814,3 +817,35 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error return pc, nil } + +func setAsyncPreemptOff(p *Target, v int64) { + logger := p.BinInfo().logger + if producer := p.BinInfo().Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 14) { + return + } + scope := globalScope(p.BinInfo(), p.BinInfo().Images[0], p.CurrentThread()) + debugv, err := scope.findGlobal("runtime", "debug") + if err != nil || debugv.Unreadable != nil { + logger.Warnf("could not find runtime/debug variable (or unreadable): %v %v", err, debugv.Unreadable) + return + } + asyncpreemptoffv, err := debugv.structMember("asyncpreemptoff") + if err != nil { + logger.Warnf("could not find asyncpreemptoff field: %v", err) + return + } + asyncpreemptoffv.loadValue(loadFullValue) + if asyncpreemptoffv.Unreadable != nil { + logger.Warnf("asyncpreemptoff field unreadable: %v", asyncpreemptoffv.Unreadable) + return + } + p.asyncPreemptChanged = true + p.asyncPreemptOff, _ = constant.Int64Val(asyncpreemptoffv.Value) + + err = scope.setValue(asyncpreemptoffv, newConstant(constant.MakeInt64(v), scope.Mem), "") + logger.Warnf("could not set asyncpreemptoff %v", err) +} + +func RestoreAsyncPreempt(p *Target) { + setAsyncPreemptOff(p, p.asyncPreemptOff) +} diff --git a/pkg/proc/target.go b/pkg/proc/target.go index a2b46de5f5..e97b117cbb 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -7,6 +7,9 @@ type Target struct { // fncallForG stores a mapping of current active function calls. fncallForG map[int]*callInjection + asyncPreemptChanged bool // runtime/debug.asyncpreemptoff was changed + asyncPreemptOff int64 // cached value of runtime/debug.asyncpreemptoff + // gcache is a cache for Goroutines that we // have read and parsed from the targets memory. // This must be cleared whenever the target is resumed. @@ -14,12 +17,17 @@ type Target struct { } // NewTarget returns an initialized Target object. -func NewTarget(p Process) *Target { +func NewTarget(p Process, disableAsyncPreempt bool) *Target { t := &Target{ Process: p, fncallForG: make(map[int]*callInjection), } t.gcache.init(p.BinInfo()) + + if disableAsyncPreempt { + setAsyncPreemptOff(t, 1) + } + return t } @@ -45,3 +53,10 @@ func (t *Target) Restart(from string) error { t.ClearAllGCache() return t.Process.Restart(from) } + +func (t *Target) Detach(kill bool) error { + if !kill && t.asyncPreemptChanged { + setAsyncPreemptOff(t, t.asyncPreemptOff) + } + return t.Process.Detach(kill) +}