diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 6e2fabf63d..259144425d 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -223,7 +223,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error // initialize for core files doesn't do much // aside from call the post initialization setup. func (p *Process) initialize(path string, debugInfoDirs []string) error { - return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint) + return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint, false) } // BinInfo will return the binary info. diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 63fc12ce31..042be9c64e 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -529,7 +529,7 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error { return err } } - if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil { + if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint, false); err != nil { p.conn.conn.Close() return err } diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index 951282c6fa..012fb968d4 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -117,8 +117,9 @@ type CommonProcess struct { goroutineCache fncallEnabled bool + fncallForG map[int]*callInjection - fncallForG map[int]*callInjection + asyncPreemptOff int64 // cached value of runtime/debug.asyncpreemptoff } type goroutineCache struct { diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 66133e0aa6..afe85b4045 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -360,7 +360,7 @@ func (dbp *Process) initialize(path string, debugInfoDirs []string) error { if err := dbp.updateThreadList(); err != nil { return err } - return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint) + return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint, runtime.GOOS == "windows") } // SetSelectedGoroutine will set internally the goroutine that should be diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index dc9667c264..1b383f0f68 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) (*Process, err } 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.common = proc.NewCommonProcess(true) @@ -65,6 +75,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err Sys: &syscall.SysProcAttr{ CreationFlags: _DEBUG_ONLY_THIS_PROCESS, }, + Env: env, } p, err = os.StartProcess(argv0Go, cmd, attr) }) @@ -473,6 +484,9 @@ 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. + proc.RestoreAsyncPreempt(dbp) 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 88504d7f7a..383b1d6955 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 @@ -52,7 +55,7 @@ func (pe ProcessDetachedError) Error() string { // PostInitializationSetup handles all of the initialization procedures // that must happen after Delve creates or attaches to a process. -func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error { +func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn, disableAsyncPreempt bool) error { entryPoint, err := p.EntryPoint() if err != nil { return err @@ -76,6 +79,10 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri p.Common().goroutineCache.init(p.BinInfo()) + if disableAsyncPreempt { + setAsyncPreemptOff(p, 1) + } + return nil } @@ -819,3 +826,34 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error return pc, nil } + +func setAsyncPreemptOff(p Process, 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.Common().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 Process) { + setAsyncPreemptOff(p, p.Common().asyncPreemptOff) +}