*: Go 1.14 support branch (#1727)

* tests: misc test fixes for go1.14

- math.go is now ambiguous due to changes to the go runtime so specify
  that we mean our own math.go in _fixtures
- go list -m requires vendor-mode to be disabled so pass '-mod=' to it
  in case user has GOFLAGS=-mod=vendor
- update version of go/packages, required to work with go 1.14 (and
  executed go mod vendor)
- Increased goroutine migration in one development version of Go 1.14
  revealed a problem with TestCheckpoints in command_test.go and
  rr_test.go. The tests were always wrong because Restart(checkpoint)
  doesn't change the current thread but we can't assume that when the
  checkpoint was taken the current goroutine was running on the same
  thread.

* goversion: update maximum supported version

* Makefile: disable testing lldb-server backend on linux with Go 1.14

There seems to be some incompatibility with lldb-server version 6.0.0
on linux and Go 1.14.

* proc/gdbserial: better handling of signals

- if multiple signals are received simultaneously propagate all of them to the
  target threads instead of only one.
- debugserver will drop an interrupt request if a target thread simultaneously
  receives a signal, handle this situation.

* dwarf/line: normalize backslashes for windows executables

Starting with Go 1.14 the compiler sometimes emits backslashes as well
as forward slashes in debug_line, normalize everything to / for
conformity with the behavior of previous versions.

* proc/native: partial support for Windows async preempt mechanism

See https://github.com/golang/go/issues/36494 for a description of why
full support for 1.14 under windows is problematic.

* proc/native: disable Go 1.14 async preemption on Windows

See https://github.com/golang/go/issues/36494
This commit is contained in:
Alessandro Arzilli 2020-02-11 02:31:54 +01:00 committed by GitHub
parent bc9d95d615
commit 0741d3e57f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 328 additions and 111 deletions

@ -63,7 +63,7 @@ func projectRoot() string {
return filepath.Join(curpath, "src", "github.com", "go-delve", "delve") return filepath.Join(curpath, "src", "github.com", "go-delve", "delve")
} }
} }
val, err := exec.Command("go", "list", "-m", "-f", "{{ .Dir }}").Output() val, err := exec.Command("go", "list", "-mod=", "-m", "-f", "{{ .Dir }}").Output()
if err != nil { if err != nil {
panic(err) // the Go tool was tested to work earlier panic(err) // the Go tool was tested to work earlier
} }

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"path/filepath" "path/filepath"
"strings"
"github.com/go-delve/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
@ -34,9 +35,12 @@ type DebugLineInfo struct {
// lastMachineCache[pc] is a state machine stopped at an address after pc // lastMachineCache[pc] is a state machine stopped at an address after pc
lastMachineCache map[uint64]*StateMachine lastMachineCache map[uint64]*StateMachine
// staticBase is the address at which the executable is loaded, 0 for non-PIEs // staticBase is the address at which the executable is loaded, 0 for non-PIEs
staticBase uint64 staticBase uint64
// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
normalizeBackslash bool
} }
type FileEntry struct { type FileEntry struct {
@ -49,7 +53,7 @@ type FileEntry struct {
type DebugLines []*DebugLineInfo type DebugLines []*DebugLineInfo
// ParseAll parses all debug_line segments found in data // ParseAll parses all debug_line segments found in data
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64) DebugLines { func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) DebugLines {
var ( var (
lines = make(DebugLines, 0) lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data) buf = bytes.NewBuffer(data)
@ -57,7 +61,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
// We have to parse multiple file name tables here. // We have to parse multiple file name tables here.
for buf.Len() > 0 { for buf.Len() > 0 {
lines = append(lines, Parse("", buf, logfn, staticBase)) lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash))
} }
return lines return lines
@ -65,7 +69,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
// Parse parses a single debug_line segment from buf. Compdir is the // Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit. // DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64) *DebugLineInfo { func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) *DebugLineInfo {
dbl := new(DebugLineInfo) dbl := new(DebugLineInfo)
dbl.Logf = logfn dbl.Logf = logfn
dbl.staticBase = staticBase dbl.staticBase = staticBase
@ -76,6 +80,7 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{})
dbl.stateMachineCache = make(map[uint64]*StateMachine) dbl.stateMachineCache = make(map[uint64]*StateMachine)
dbl.lastMachineCache = make(map[uint64]*StateMachine) dbl.lastMachineCache = make(map[uint64]*StateMachine)
dbl.normalizeBackslash = normalizeBackslash
parseDebugLinePrologue(dbl, buf) parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf) parseIncludeDirs(dbl, buf)
@ -139,6 +144,10 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool)
return entry return entry
} }
if info.normalizeBackslash {
entry.Path = strings.Replace(entry.Path, "\\", "/", -1)
}
entry.DirIdx, _ = util.DecodeULEB128(buf) entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf) entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf) entry.Length, _ = util.DecodeULEB128(buf)

@ -67,7 +67,7 @@ const (
func testDebugLinePrologueParser(p string, t *testing.T) { func testDebugLinePrologueParser(p string, t *testing.T) {
data := grabDebugLineSection(p, t) data := grabDebugLineSection(p, t)
debugLines := ParseAll(data, nil, 0) debugLines := ParseAll(data, nil, 0, true)
mainFileFound := false mainFileFound := false
@ -164,7 +164,7 @@ func BenchmarkLineParser(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = ParseAll(data, nil, 0) _ = ParseAll(data, nil, 0, true)
} }
} }
@ -179,7 +179,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
tb.Fatal("Could not read test data", err) tb.Fatal("Could not read test data", err)
} }
return ParseAll(data, nil, 0) return ParseAll(data, nil, 0, true)
} }
func BenchmarkStateMachine(b *testing.B) { func BenchmarkStateMachine(b *testing.B) {

@ -80,7 +80,7 @@ func TestGrafana(t *testing.T) {
} }
cuname, _ := e.Val(dwarf.AttrName).(string) cuname, _ := e.Val(dwarf.AttrName).(string)
lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0) lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false)
sm := newStateMachine(lineInfo, lineInfo.Instructions) sm := newStateMachine(lineInfo, lineInfo.Instructions)
lnrdr, err := data.LineReader(e) lnrdr, err := data.LineReader(e)

@ -8,7 +8,7 @@ var (
MinSupportedVersionOfGoMajor = 1 MinSupportedVersionOfGoMajor = 1
MinSupportedVersionOfGoMinor = 11 MinSupportedVersionOfGoMinor = 11
MaxSupportedVersionOfGoMajor = 1 MaxSupportedVersionOfGoMajor = 1
MaxSupportedVersionOfGoMinor = 13 MaxSupportedVersionOfGoMinor = 14
goTooOldErr = fmt.Errorf("Version of Go is too old for this version of Delve (minimum supported version %d.%d, suppress this error with --check-go-version=false)", MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor) goTooOldErr = fmt.Errorf("Version of Go is too old for this version of Delve (minimum supported version %d.%d, suppress this error with --check-go-version=false)", MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor)
dlvTooOldErr = fmt.Errorf("Version of Delve is too old for this version of Go (maximum supported version %d.%d, suppress this error with --check-go-version=false)", MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor) dlvTooOldErr = fmt.Errorf("Version of Delve is too old for this version of Go (maximum supported version %d.%d, suppress this error with --check-go-version=false)", MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor)
) )

@ -1327,7 +1327,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
logger.Printf(fmt, args) logger.Printf(fmt, args)
} }
} }
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase) cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows")
} }
cu.producer, _ = entry.Val(dwarf.AttrProducer).(string) cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
if cu.isgo && cu.producer != "" { if cu.isgo && cu.producer != "" {

@ -216,7 +216,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
return nil, err return nil, err
} }
return proc.NewTarget(p), nil return proc.NewTarget(p, false), nil
} }
// initialize for core files doesn't do much // initialize for core files doesn't do much

@ -135,7 +135,8 @@ type Thread struct {
regs gdbRegisters regs gdbRegisters
CurrentBreakpoint proc.BreakpointState CurrentBreakpoint proc.BreakpointState
p *Process p *Process
setbp bool // thread was stopped because of a breakpoint sig uint8 // signal received by thread after last stop
setbp bool // thread was stopped because of a breakpoint
common proc.CommonThread common proc.CommonThread
} }
@ -405,7 +406,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
if err != nil { if err != nil {
return nil, err 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 // LLDBAttach starts an instance of lldb-server and connects to it, asking
@ -457,7 +458,7 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
return proc.NewTarget(p), nil return proc.NewTarget(p, false), nil
} }
// EntryPoint will return the process entry point address, useful for // EntryPoint will return the process entry point address, useful for
@ -646,13 +647,15 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
// resume all threads // resume all threads
var threadID string var threadID string
var sig uint8 var trapthread *Thread
var tu = threadUpdater{p: p} var tu = threadUpdater{p: p}
var err error
continueLoop: continueLoop:
for { for {
var err error
var sig uint8
tu.Reset() tu.Reset()
threadID, sig, err = p.conn.resume(sig, threadID, &tu) //TODO: pass thread list to resume, which should use it to pass the correct signals
threadID, sig, err = p.conn.resume(p.threads, &tu)
if err != nil { if err != nil {
if _, exited := err.(proc.ErrProcessExited); exited { if _, exited := err.(proc.ErrProcessExited); exited {
p.exited = true p.exited = true
@ -660,45 +663,25 @@ continueLoop:
return nil, err return nil, err
} }
// 0x5 is always a breakpoint, a manual stop either manifests as 0x13 // For stubs that support qThreadStopInfo updateThreadListNoRegisters will
// (lldb), 0x11 (debugserver) or 0x2 (gdbserver). // find out the reason why each thread stopped.
// Since 0x2 could also be produced by the user p.updateThreadListNoRegisters(&tu)
// pressing ^C (in which case it should be passed to the inferior) we need
// the ctrlC flag to know that we are the originators. trapthread = p.findThreadByStrID(threadID)
switch sig { if trapthread != nil && !p.threadStopInfo {
case interruptSignal: // interrupt // For stubs that do not support qThreadStopInfo we manually set the
if p.getCtrlC() { // reason the thread returned by resume() stopped.
break continueLoop trapthread.sig = sig
} }
case breakpointSignal: // breakpoint
var shouldStop bool
trapthread, shouldStop = p.handleThreadSignals(trapthread)
if shouldStop {
break continueLoop break continueLoop
case childSignal: // stop on debugserver but SIGCHLD on lldb-server/linux
if p.conn.isDebugserver {
break continueLoop
}
case stopSignal: // stop
break continueLoop
// The following are fake BSD-style signals sent by debugserver
// Unfortunately debugserver can not convert them into signals for the
// process so we must stop here.
case debugServerTargetExcBadAccess, debugServerTargetExcBadInstruction, debugServerTargetExcArithmetic, debugServerTargetExcEmulation, debugServerTargetExcSoftware, debugServerTargetExcBreakpoint:
break continueLoop
// Signal 0 is returned by rr when it reaches the start of the process
// in backward continue mode.
case 0:
if p.conn.direction == proc.Backward {
break continueLoop
}
default:
// any other signal is always propagated to inferior
} }
} }
if err := p.updateThreadList(&tu); err != nil { if err := p.updateThreadRegisters(); err != nil {
return nil, err return nil, err
} }
@ -712,28 +695,115 @@ continueLoop:
return nil, err return nil, err
} }
if trapthread == nil {
return nil, fmt.Errorf("could not find thread %s", threadID)
}
var err error
switch trapthread.sig {
case 0x91:
err = errors.New("bad access")
case 0x92:
err = errors.New("bad instruction")
case 0x93:
err = errors.New("arithmetic exception")
case 0x94:
err = errors.New("emulation exception")
case 0x95:
err = errors.New("software exception")
case 0x96:
err = errors.New("breakpoint exception")
}
if err != nil {
// the signals that are reported here can not be propagated back to the target process.
trapthread.sig = 0
}
return trapthread, err
}
func (p *Process) findThreadByStrID(threadID string) *Thread {
for _, thread := range p.threads { for _, thread := range p.threads {
if thread.strID == threadID { if thread.strID == threadID {
var err error return thread
switch sig { }
case 0x91: }
err = errors.New("bad access") return nil
case 0x92: }
err = errors.New("bad instruction")
case 0x93: // handleThreadSignals looks at the signals received by each thread and
err = errors.New("arithmetic exception") // decides which ones to mask and which ones to propagate back to the target
case 0x94: // and returns true if we should stop execution in response to one of the
err = errors.New("emulation exception") // signals and return control to the user.
case 0x95: // Adjusts trapthread to a thread that we actually want to stop at.
err = errors.New("software exception") func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread, shouldStop bool) {
case 0x96: var trapthreadCandidate *Thread
err = errors.New("breakpoint exception")
for _, th := range p.threads {
isStopSignal := false
// 0x5 is always a breakpoint, a manual stop either manifests as 0x13
// (lldb), 0x11 (debugserver) or 0x2 (gdbserver).
// Since 0x2 could also be produced by the user
// pressing ^C (in which case it should be passed to the inferior) we need
// the ctrlC flag to know that we are the originators.
switch th.sig {
case interruptSignal: // interrupt
if p.getCtrlC() {
isStopSignal = true
} }
return thread, err case breakpointSignal: // breakpoint
isStopSignal = true
case childSignal: // stop on debugserver but SIGCHLD on lldb-server/linux
if p.conn.isDebugserver {
isStopSignal = true
}
case stopSignal: // stop
isStopSignal = true
// The following are fake BSD-style signals sent by debugserver
// Unfortunately debugserver can not convert them into signals for the
// process so we must stop here.
case debugServerTargetExcBadAccess, debugServerTargetExcBadInstruction, debugServerTargetExcArithmetic, debugServerTargetExcEmulation, debugServerTargetExcSoftware, debugServerTargetExcBreakpoint:
trapthreadCandidate = th
shouldStop = true
// Signal 0 is returned by rr when it reaches the start of the process
// in backward continue mode.
case 0:
if p.conn.direction == proc.Backward {
isStopSignal = true
}
default:
// any other signal is always propagated to inferior
}
if isStopSignal {
if trapthreadCandidate == nil {
trapthreadCandidate = th
}
th.sig = 0
shouldStop = true
} }
} }
return nil, fmt.Errorf("could not find thread %s", threadID) if (trapthread == nil || trapthread.sig != 0) && trapthreadCandidate != nil {
// proc.Continue wants us to return one of the threads that we should stop
// at, if the thread returned by vCont received a signal that we want to
// propagate back to the target thread but there were also other threads
// that we wish to stop at we should pick one of those.
trapthread = trapthreadCandidate
}
if p.getCtrlC() || p.getManualStopRequested() {
// If we request an interrupt and a target thread simultaneously receives
// an unrelated singal debugserver will discard our interrupt request and
// report the signal but we should stop anyway.
shouldStop = true
}
return trapthread, shouldStop
} }
// SetSelectedGoroutine will set internally the goroutine that should be // SetSelectedGoroutine will set internally the goroutine that should be
@ -792,6 +862,13 @@ func (p *Process) CheckAndClearManualStopRequest() bool {
return msr return msr
} }
func (p *Process) getManualStopRequested() bool {
p.conn.manualStopMutex.Lock()
msr := p.manualStopRequested
p.conn.manualStopMutex.Unlock()
return msr
}
func (p *Process) setCtrlC(v bool) { func (p *Process) setCtrlC(v bool) {
p.conn.manualStopMutex.Lock() p.conn.manualStopMutex.Lock()
p.ctrlC = v p.ctrlC = v
@ -854,7 +931,7 @@ func (p *Process) Restart(pos string) error {
// for some reason we have to send a vCont;c after a vRun to make rr behave // for some reason we have to send a vCont;c after a vRun to make rr behave
// properly, because that's what gdb does. // properly, because that's what gdb does.
_, _, err = p.conn.resume(0, "", nil) _, _, err = p.conn.resume(nil, nil)
if err != nil { if err != nil {
return err return err
} }
@ -1081,7 +1158,7 @@ func (tu *threadUpdater) Finish() {
continue continue
} }
delete(tu.p.threads, threadID) delete(tu.p.threads, threadID)
if tu.p.currentThread.ID == threadID { if tu.p.currentThread != nil && tu.p.currentThread.ID == threadID {
tu.p.currentThread = nil tu.p.currentThread = nil
} }
} }
@ -1099,14 +1176,13 @@ func (tu *threadUpdater) Finish() {
} }
} }
// updateThreadsList retrieves the list of inferior threads from the stub // updateThreadListNoRegisters retrieves the list of inferior threads from
// and passes it to the threadUpdater. // the stub and passes it to threadUpdater.
// Then it reloads the register information for all running threads.
// Some stubs will return the list of running threads in the stop packet, if // Some stubs will return the list of running threads in the stop packet, if
// this happens the threadUpdater will know that we have already updated the // this happens the threadUpdater will know that we have already updated the
// thread list and the first step of updateThreadList will be skipped. // thread list and the first step of updateThreadList will be skipped.
// Registers are always reloaded. // Registers are always reloaded.
func (p *Process) updateThreadList(tu *threadUpdater) error { func (p *Process) updateThreadListNoRegisters(tu *threadUpdater) error {
if !tu.done { if !tu.done {
first := true first := true
for { for {
@ -1126,8 +1202,8 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
tu.Finish() tu.Finish()
} }
if p.threadStopInfo { for _, th := range p.threads {
for _, th := range p.threads { if p.threadStopInfo {
sig, reason, err := p.conn.threadStopInfo(th.strID) sig, reason, err := p.conn.threadStopInfo(th.strID)
if err != nil { if err != nil {
if isProtocolErrorUnsupported(err) { if isProtocolErrorUnsupported(err) {
@ -1137,9 +1213,18 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
return err return err
} }
th.setbp = (reason == "breakpoint" || (reason == "" && sig == breakpointSignal)) th.setbp = (reason == "breakpoint" || (reason == "" && sig == breakpointSignal))
th.sig = sig
} else {
th.sig = 0
} }
} }
return nil
}
// updateThreadRegisters reloads register informations for all running
// threads.
func (p *Process) updateThreadRegisters() error {
for _, thread := range p.threads { for _, thread := range p.threads {
if err := thread.reloadRegisters(); err != nil { if err := thread.reloadRegisters(); err != nil {
return err return err
@ -1148,6 +1233,24 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
return nil return nil
} }
// updateThreadsList retrieves the list of inferior threads from the stub
// and passes it to the threadUpdater.
// Then it reloads the register information for all running threads.
// Some stubs will return the list of running threads in the stop packet, if
// this happens the threadUpdater will know that we have already updated the
// thread list and the first step of updateThreadList will be skipped.
// Registers are always reloaded.
func (p *Process) updateThreadList(tu *threadUpdater) error {
err := p.updateThreadListNoRegisters(tu)
if err != nil {
return err
}
for _, th := range p.threads {
th.sig = 0
}
return p.updateThreadRegisters()
}
func (p *Process) setCurrentBreakpoints() error { func (p *Process) setCurrentBreakpoints() error {
if p.threadStopInfo { if p.threadStopInfo {
for _, th := range p.threads { for _, th := range p.threads {

@ -538,17 +538,23 @@ func (conn *gdbConn) writeRegister(threadID string, regnum int, data []byte) err
return err return err
} }
// resume executes a 'vCont' command on all threads with action 'c' if sig // resume execution of the target process.
// is 0 or 'C' if it isn't. If sig isn't 0 a threadID should be specified as // If the current direction is proc.Backward this is done with the 'bc' command.
// the target of the signal. // If the current direction is proc.Forward this is done with the vCont command.
func (conn *gdbConn) resume(sig uint8, threadID string, tu *threadUpdater) (string, uint8, error) { // The threads argument will be used to determine which signal to use to
// resume each thread. If a thread has sig == 0 the 'c' action will be used,
// otherwise the 'C' action will be used and the value of sig will be passed
// to it.
func (conn *gdbConn) resume(threads map[int]*Thread, tu *threadUpdater) (string, uint8, error) {
if conn.direction == proc.Forward { if conn.direction == proc.Forward {
conn.outbuf.Reset() conn.outbuf.Reset()
if sig == 0 { fmt.Fprintf(&conn.outbuf, "$vCont")
fmt.Fprint(&conn.outbuf, "$vCont;c") for _, th := range threads {
} else { if th.sig != 0 {
fmt.Fprintf(&conn.outbuf, "$vCont;C%02x:%s;c", sig, threadID) fmt.Fprintf(&conn.outbuf, ";C%02x:%s", th.sig, th.strID)
}
} }
fmt.Fprintf(&conn.outbuf, ";c")
} else { } else {
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil { if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
return "", 0, err return "", 0, err

@ -96,7 +96,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
return nil, err return nil, err
} }
return proc.NewTarget(p), nil return proc.NewTarget(p, false), nil
} }
// ErrPerfEventParanoid is the error returned by Reply and Record if // ErrPerfEventParanoid is the error returned by Reply and Record if

@ -197,7 +197,7 @@ func TestCheckpoints(t *testing.T) {
bp := setFunctionBreakpoint(p, t, "main.main") bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue") assertNoError(proc.Continue(p), t, "Continue")
when0, loc0 := getPosition(p, t) when0, loc0 := getPosition(p, t)
t.Logf("when0: %q (%#x)", when0, loc0.PC) t.Logf("when0: %q (%#x) %x", when0, loc0.PC, p.CurrentThread().ThreadID())
// Create a checkpoint and check that the list of checkpoints reflects this // Create a checkpoint and check that the list of checkpoints reflects this
cpid, err := p.Checkpoint("checkpoint1") cpid, err := p.Checkpoint("checkpoint1")
@ -215,7 +215,7 @@ func TestCheckpoints(t *testing.T) {
assertNoError(proc.Next(p), t, "First Next") assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next") assertNoError(proc.Next(p), t, "Second Next")
when1, loc1 := getPosition(p, t) when1, loc1 := getPosition(p, t)
t.Logf("when1: %q (%#x)", when1, loc1.PC) t.Logf("when1: %q (%#x) %x", when1, loc1.PC, p.CurrentThread().ThreadID())
if loc0.PC == loc1.PC { if loc0.PC == loc1.PC {
t.Fatalf("next did not move process %#x", loc0.PC) t.Fatalf("next did not move process %#x", loc0.PC)
} }
@ -226,8 +226,10 @@ func TestCheckpoints(t *testing.T) {
// Move back to checkpoint, check that the output of 'when' is the same as // Move back to checkpoint, check that the output of 'when' is the same as
// what it was when we set the breakpoint // what it was when we set the breakpoint
p.Restart(fmt.Sprintf("c%d", cpid)) p.Restart(fmt.Sprintf("c%d", cpid))
g, _ := proc.FindGoroutine(p, 1)
p.SwitchGoroutine(g)
when2, loc2 := getPosition(p, t) when2, loc2 := getPosition(p, t)
t.Logf("when2: %q (%#x)", when2, loc2.PC) t.Logf("when2: %q (%#x) %x", when2, loc2.PC, p.CurrentThread().ThreadID())
if loc2.PC != loc0.PC { if loc2.PC != loc0.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC) t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC)
} }
@ -252,6 +254,8 @@ func TestCheckpoints(t *testing.T) {
_, err = p.ClearBreakpoint(bp.Addr) _, err = p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint") assertNoError(err, t, "ClearBreakpoint")
p.Restart(fmt.Sprintf("c%d", cpid)) p.Restart(fmt.Sprintf("c%d", cpid))
g, _ = proc.FindGoroutine(p, 1)
p.SwitchGoroutine(g)
assertNoError(proc.Next(p), t, "First Next") assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next") assertNoError(proc.Next(p), t, "Second Next")
when4, loc4 := getPosition(p, t) when4, loc4 := getPosition(p, t)

@ -126,7 +126,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
return nil, err return nil, err
} }
return proc.NewTarget(dbp), err return proc.NewTarget(dbp, false), err
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
@ -158,7 +158,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
} }
return proc.NewTarget(dbp), nil return proc.NewTarget(dbp, false), nil
} }
// Kill kills the process. // Kill kills the process.

@ -87,7 +87,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err 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 // 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) dbp.Detach(false)
return nil, err return nil, err
} }
return proc.NewTarget(dbp), nil return proc.NewTarget(dbp, false), nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {

@ -92,7 +92,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err 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 // 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 { if err != nil {
return nil, err return nil, err
} }
return proc.NewTarget(dbp), nil return proc.NewTarget(dbp, false), nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {

@ -7,6 +7,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
"unsafe" "unsafe"
@ -55,6 +56,15 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
} }
closer.Close() 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 var p *os.Process
dbp := New(0) dbp := New(0)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
@ -64,6 +74,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
Sys: &syscall.SysProcAttr{ Sys: &syscall.SysProcAttr{
CreationFlags: _DEBUG_ONLY_THIS_PROCESS, CreationFlags: _DEBUG_ONLY_THIS_PROCESS,
}, },
Env: env,
} }
p, err = os.StartProcess(argv0Go, cmd, attr) 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) dbp.Detach(true)
return nil, err return nil, err
} }
return proc.NewTarget(dbp), nil return proc.NewTarget(dbp, true), nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {
@ -168,7 +179,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
dbp.Detach(true) dbp.Detach(true)
return nil, err return nil, err
} }
return proc.NewTarget(dbp), nil return proc.NewTarget(dbp, true), nil
} }
// kill kills the process. // kill kills the process.
@ -472,6 +483,8 @@ func (dbp *Process) stop(trapthread *Thread) (err error) {
func (dbp *Process) detach(kill bool) error { func (dbp *Process) detach(kill bool) error {
if !kill { 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 { for _, thread := range dbp.threads {
_, err := _ResumeThread(thread.os.hThread) _, err := _ResumeThread(thread.os.hThread)
if err != nil { if err != nil {

@ -36,9 +36,22 @@ func (t *Thread) singleStep() error {
return err return err
} }
_, err = _ResumeThread(t.os.hThread) suspendcnt := 0
if err != nil {
return err // If a thread simultaneously hits a breakpoint and is suspended by the Go
// runtime it will have a suspend count greater than 1 and to actually take
// a single step we have to resume it multiple times here.
// We keep a counter of how many times it was suspended so that after
// single-stepping we can re-suspend it the corrent number of times.
for {
n, err := _ResumeThread(t.os.hThread)
if err != nil {
return err
}
suspendcnt++
if n == 1 {
break
}
} }
for { for {
@ -63,9 +76,11 @@ func (t *Thread) singleStep() error {
}) })
} }
_, err = _SuspendThread(t.os.hThread) for i := 0; i < suspendcnt; i++ {
if err != nil { _, err = _SuspendThread(t.os.hThread)
return err if err != nil {
return err
}
} }
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {

@ -5,9 +5,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/constant"
"go/token" "go/token"
"path/filepath" "path/filepath"
"strconv" "strconv"
"github.com/go-delve/delve/pkg/goversion"
) )
// ErrNotExecutable is returned after attempting to execute a non-executable file // ErrNotExecutable is returned after attempting to execute a non-executable file
@ -814,3 +817,31 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error
return pc, nil 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)
}

@ -1244,7 +1244,7 @@ func TestFrameEvaluation(t *testing.T) {
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err) t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
continue continue
} }
t.Logf("Goroutine %d", g.ID) t.Logf("Goroutine %d %#v", g.ID, g.Thread)
logStacktrace(t, p.BinInfo(), frames) logStacktrace(t, p.BinInfo(), frames)
for i := range frames { for i := range frames {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" { if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {

@ -7,6 +7,9 @@ type Target struct {
// fncallForG stores a mapping of current active function calls. // fncallForG stores a mapping of current active function calls.
fncallForG map[int]*callInjection 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 // gcache is a cache for Goroutines that we
// have read and parsed from the targets memory. // have read and parsed from the targets memory.
// This must be cleared whenever the target is resumed. // This must be cleared whenever the target is resumed.
@ -14,12 +17,17 @@ type Target struct {
} }
// NewTarget returns an initialized Target object. // NewTarget returns an initialized Target object.
func NewTarget(p Process) *Target { func NewTarget(p Process, disableAsyncPreempt bool) *Target {
t := &Target{ t := &Target{
Process: p, Process: p,
fncallForG: make(map[int]*callInjection), fncallForG: make(map[int]*callInjection),
} }
t.gcache.init(p.BinInfo()) t.gcache.init(p.BinInfo())
if disableAsyncPreempt {
setAsyncPreemptOff(t, 1)
}
return t return t
} }
@ -45,3 +53,10 @@ func (t *Target) Restart(from string) error {
t.ClearAllGCache() t.ClearAllGCache()
return t.Process.Restart(from) 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)
}

@ -271,8 +271,8 @@ func TestIssue411(t *testing.T) {
} }
test.AllowRecording(t) test.AllowRecording(t)
withTestTerminal("math", t, func(term *FakeTerminal) { withTestTerminal("math", t, func(term *FakeTerminal) {
term.MustExec("break math.go:8") term.MustExec("break _fixtures/math.go:8")
term.MustExec("trace math.go:9") term.MustExec("trace _fixtures/math.go:9")
term.MustExec("continue") term.MustExec("continue")
out := term.MustExec("next") out := term.MustExec("next")
if !strings.HasPrefix(out, "> main.main()") { if !strings.HasPrefix(out, "> main.main()") {
@ -683,7 +683,9 @@ func TestCheckpoints(t *testing.T) {
term.MustExec("checkpoints") term.MustExec("checkpoints")
listIsAt(t, term, "next", 17, -1, -1) listIsAt(t, term, "next", 17, -1, -1)
listIsAt(t, term, "next", 18, -1, -1) listIsAt(t, term, "next", 18, -1, -1)
listIsAt(t, term, "restart c1", 16, -1, -1) term.MustExec("restart c1")
term.MustExec("goroutine 1")
listIsAt(t, term, "list", 16, -1, -1)
}) })
} }

@ -10,6 +10,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/go-delve/delve/pkg/goversion"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -71,7 +72,7 @@ func NewMakeCommands() *cobra.Command {
Use the flags -s, -r and -b to specify which tests to run. Specifying nothing is equivalent to: Use the flags -s, -r and -b to specify which tests to run. Specifying nothing is equivalent to:
go run scripts/make.go test -s all -b default go run scripts/make.go test -s all -b default
go run scripts/make.go test -s basic -b lldb # if lldb-server is installed go run scripts/make.go test -s basic -b lldb # if lldb-server is installed and Go < 1.14
go run scripts/make.go test -s basic -b rr # if rr is installed go run scripts/make.go test -s basic -b rr # if rr is installed
go run scripts/make.go test -s basic -m pie # only on linux go run scripts/make.go test -s basic -m pie # only on linux
@ -295,7 +296,7 @@ func testCmd(cmd *cobra.Command, args []string) {
fmt.Println("Testing default backend") fmt.Println("Testing default backend")
testCmdIntl("all", "", "default", "normal") testCmdIntl("all", "", "default", "normal")
if inpath("lldb-server") { if inpath("lldb-server") && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
fmt.Println("\nTesting LLDB backend") fmt.Println("\nTesting LLDB backend")
testCmdIntl("basic", "", "lldb", "normal") testCmdIntl("basic", "", "lldb", "normal")
} }

@ -1205,7 +1205,6 @@ func TestCallFunction(t *testing.T) {
// support calling optimized functions // support calling optimized functions
{`strings.Join(nil, "")`, []string{`:string:""`}, nil}, {`strings.Join(nil, "")`, []string{`:string:""`}, nil},
{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil}, {`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
{`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument a in function strings.Join: could not find symbol value for s1`)},
{`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")}, {`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
{`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil}, {`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil},
{`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil}, {`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil},
@ -1230,6 +1229,14 @@ func TestCallFunction(t *testing.T) {
{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method {`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
} }
var testcasesBefore114After112 = []testCaseCallFunction{
{`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument a in function strings.Join: could not find symbol value for s1`)},
}
var testcases114 = []testCaseCallFunction{
{`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument elems in function strings.Join: could not find symbol value for s1`)},
}
withTestProcess("fncall", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("fncall", t, func(p *proc.Target, fixture protest.Fixture) {
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", 0) _, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", 0)
if err != nil { if err != nil {
@ -1244,6 +1251,11 @@ func TestCallFunction(t *testing.T) {
for _, tc := range testcases112 { for _, tc := range testcases112 {
testCallFunction(t, p, tc) testCallFunction(t, p, tc)
} }
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcasesBefore114After112 {
testCallFunction(t, p, tc)
}
}
} }
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
@ -1252,6 +1264,12 @@ func TestCallFunction(t *testing.T) {
} }
} }
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcases114 {
testCallFunction(t, p, tc)
}
}
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!! // LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil}) testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
}) })