Skip to content

Commit

Permalink
proc/native: disable Go 1.14 async preemption on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
aarzilli committed Jan 24, 2020
1 parent b151567 commit 215c395
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 15 deletions.
2 changes: 1 addition & 1 deletion pkg/proc/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/gdbserial/gdbserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/gdbserial/rr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions pkg/proc/gdbserial/rr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/proc_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/proc_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/proc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 15 additions & 2 deletions pkg/proc/native/proc_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"unsafe"

Expand Down Expand Up @@ -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() {
Expand All @@ -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)
})
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions pkg/proc/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
17 changes: 16 additions & 1 deletion pkg/proc/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,27 @@ 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.
gcache goroutineCache
}

// 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
}

Expand All @@ -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)
}

0 comments on commit 215c395

Please sign in to comment.