*: 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:
parent
bc9d95d615
commit
0741d3e57f
@ -63,7 +63,7 @@ func projectRoot() string {
|
||||
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 {
|
||||
panic(err) // the Go tool was tested to work earlier
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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 map[uint64]*StateMachine
|
||||
|
||||
|
||||
// staticBase is the address at which the executable is loaded, 0 for non-PIEs
|
||||
staticBase uint64
|
||||
|
||||
// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
|
||||
normalizeBackslash bool
|
||||
}
|
||||
|
||||
type FileEntry struct {
|
||||
@ -49,7 +53,7 @@ type FileEntry struct {
|
||||
type DebugLines []*DebugLineInfo
|
||||
|
||||
// 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 (
|
||||
lines = make(DebugLines, 0)
|
||||
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.
|
||||
for buf.Len() > 0 {
|
||||
lines = append(lines, Parse("", buf, logfn, staticBase))
|
||||
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash))
|
||||
}
|
||||
|
||||
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
|
||||
// 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.Logf = logfn
|
||||
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.lastMachineCache = make(map[uint64]*StateMachine)
|
||||
dbl.normalizeBackslash = normalizeBackslash
|
||||
|
||||
parseDebugLinePrologue(dbl, buf)
|
||||
parseIncludeDirs(dbl, buf)
|
||||
@ -139,6 +144,10 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool)
|
||||
return entry
|
||||
}
|
||||
|
||||
if info.normalizeBackslash {
|
||||
entry.Path = strings.Replace(entry.Path, "\\", "/", -1)
|
||||
}
|
||||
|
||||
entry.DirIdx, _ = util.DecodeULEB128(buf)
|
||||
entry.LastModTime, _ = util.DecodeULEB128(buf)
|
||||
entry.Length, _ = util.DecodeULEB128(buf)
|
||||
|
@ -67,7 +67,7 @@ const (
|
||||
|
||||
func testDebugLinePrologueParser(p string, t *testing.T) {
|
||||
data := grabDebugLineSection(p, t)
|
||||
debugLines := ParseAll(data, nil, 0)
|
||||
debugLines := ParseAll(data, nil, 0, true)
|
||||
|
||||
mainFileFound := false
|
||||
|
||||
@ -164,7 +164,7 @@ func BenchmarkLineParser(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
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)
|
||||
}
|
||||
|
||||
return ParseAll(data, nil, 0)
|
||||
return ParseAll(data, nil, 0, true)
|
||||
}
|
||||
|
||||
func BenchmarkStateMachine(b *testing.B) {
|
||||
|
@ -80,7 +80,7 @@ func TestGrafana(t *testing.T) {
|
||||
}
|
||||
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)
|
||||
|
||||
lnrdr, err := data.LineReader(e)
|
||||
|
@ -8,7 +8,7 @@ var (
|
||||
MinSupportedVersionOfGoMajor = 1
|
||||
MinSupportedVersionOfGoMinor = 11
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
if cu.isgo && cu.producer != "" {
|
||||
|
@ -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
|
||||
|
@ -135,7 +135,8 @@ type Thread struct {
|
||||
regs gdbRegisters
|
||||
CurrentBreakpoint proc.BreakpointState
|
||||
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
|
||||
}
|
||||
|
||||
@ -405,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
|
||||
@ -457,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
|
||||
@ -646,13 +647,15 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
|
||||
|
||||
// resume all threads
|
||||
var threadID string
|
||||
var sig uint8
|
||||
var trapthread *Thread
|
||||
var tu = threadUpdater{p: p}
|
||||
var err error
|
||||
continueLoop:
|
||||
for {
|
||||
var err error
|
||||
var sig uint8
|
||||
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 _, exited := err.(proc.ErrProcessExited); exited {
|
||||
p.exited = true
|
||||
@ -660,45 +663,25 @@ continueLoop:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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 sig {
|
||||
case interruptSignal: // interrupt
|
||||
if p.getCtrlC() {
|
||||
break continueLoop
|
||||
}
|
||||
case breakpointSignal: // breakpoint
|
||||
// For stubs that support qThreadStopInfo updateThreadListNoRegisters will
|
||||
// find out the reason why each thread stopped.
|
||||
p.updateThreadListNoRegisters(&tu)
|
||||
|
||||
trapthread = p.findThreadByStrID(threadID)
|
||||
if trapthread != nil && !p.threadStopInfo {
|
||||
// For stubs that do not support qThreadStopInfo we manually set the
|
||||
// reason the thread returned by resume() stopped.
|
||||
trapthread.sig = sig
|
||||
}
|
||||
|
||||
var shouldStop bool
|
||||
trapthread, shouldStop = p.handleThreadSignals(trapthread)
|
||||
if shouldStop {
|
||||
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
|
||||
}
|
||||
|
||||
@ -712,28 +695,115 @@ continueLoop:
|
||||
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 {
|
||||
if thread.strID == threadID {
|
||||
var err error
|
||||
switch 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")
|
||||
return thread
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleThreadSignals looks at the signals received by each thread and
|
||||
// decides which ones to mask and which ones to propagate back to the target
|
||||
// and returns true if we should stop execution in response to one of the
|
||||
// signals and return control to the user.
|
||||
// Adjusts trapthread to a thread that we actually want to stop at.
|
||||
func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread, shouldStop bool) {
|
||||
var trapthreadCandidate *Thread
|
||||
|
||||
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
|
||||
@ -792,6 +862,13 @@ func (p *Process) CheckAndClearManualStopRequest() bool {
|
||||
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) {
|
||||
p.conn.manualStopMutex.Lock()
|
||||
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
|
||||
// properly, because that's what gdb does.
|
||||
_, _, err = p.conn.resume(0, "", nil)
|
||||
_, _, err = p.conn.resume(nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1081,7 +1158,7 @@ func (tu *threadUpdater) Finish() {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1099,14 +1176,13 @@ func (tu *threadUpdater) Finish() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// updateThreadListNoRegisters retrieves the list of inferior threads from
|
||||
// the stub and passes it to threadUpdater.
|
||||
// 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 {
|
||||
func (p *Process) updateThreadListNoRegisters(tu *threadUpdater) error {
|
||||
if !tu.done {
|
||||
first := true
|
||||
for {
|
||||
@ -1126,8 +1202,8 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
|
||||
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)
|
||||
if err != nil {
|
||||
if isProtocolErrorUnsupported(err) {
|
||||
@ -1137,9 +1213,18 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
if err := thread.reloadRegisters(); err != nil {
|
||||
return err
|
||||
@ -1148,6 +1233,24 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
|
||||
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 {
|
||||
if p.threadStopInfo {
|
||||
for _, th := range p.threads {
|
||||
|
@ -538,17 +538,23 @@ func (conn *gdbConn) writeRegister(threadID string, regnum int, data []byte) err
|
||||
return err
|
||||
}
|
||||
|
||||
// resume executes a 'vCont' command on all threads with action 'c' if sig
|
||||
// is 0 or 'C' if it isn't. If sig isn't 0 a threadID should be specified as
|
||||
// the target of the signal.
|
||||
func (conn *gdbConn) resume(sig uint8, threadID string, tu *threadUpdater) (string, uint8, error) {
|
||||
// resume execution of the target process.
|
||||
// If the current direction is proc.Backward this is done with the 'bc' command.
|
||||
// If the current direction is proc.Forward this is done with the vCont command.
|
||||
// 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 {
|
||||
conn.outbuf.Reset()
|
||||
if sig == 0 {
|
||||
fmt.Fprint(&conn.outbuf, "$vCont;c")
|
||||
} else {
|
||||
fmt.Fprintf(&conn.outbuf, "$vCont;C%02x:%s;c", sig, threadID)
|
||||
fmt.Fprintf(&conn.outbuf, "$vCont")
|
||||
for _, th := range threads {
|
||||
if th.sig != 0 {
|
||||
fmt.Fprintf(&conn.outbuf, ";C%02x:%s", th.sig, th.strID)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&conn.outbuf, ";c")
|
||||
} else {
|
||||
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
|
||||
return "", 0, err
|
||||
|
@ -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
|
||||
|
@ -197,7 +197,7 @@ func TestCheckpoints(t *testing.T) {
|
||||
bp := setFunctionBreakpoint(p, t, "main.main")
|
||||
assertNoError(proc.Continue(p), t, "Continue")
|
||||
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
|
||||
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, "Second Next")
|
||||
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 {
|
||||
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
|
||||
// what it was when we set the breakpoint
|
||||
p.Restart(fmt.Sprintf("c%d", cpid))
|
||||
g, _ := proc.FindGoroutine(p, 1)
|
||||
p.SwitchGoroutine(g)
|
||||
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 {
|
||||
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)
|
||||
assertNoError(err, t, "ClearBreakpoint")
|
||||
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, "Second Next")
|
||||
when4, loc4 := getPosition(p, t)
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -36,9 +36,22 @@ func (t *Thread) singleStep() error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = _ResumeThread(t.os.hThread)
|
||||
if err != nil {
|
||||
return err
|
||||
suspendcnt := 0
|
||||
|
||||
// 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 {
|
||||
@ -63,9 +76,11 @@ func (t *Thread) singleStep() error {
|
||||
})
|
||||
}
|
||||
|
||||
_, err = _SuspendThread(t.os.hThread)
|
||||
if err != nil {
|
||||
return err
|
||||
for i := 0; i < suspendcnt; i++ {
|
||||
_, err = _SuspendThread(t.os.hThread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t.dbp.execPtraceFunc(func() {
|
||||
|
@ -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,31 @@ 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)
|
||||
}
|
||||
|
@ -1244,7 +1244,7 @@ func TestFrameEvaluation(t *testing.T) {
|
||||
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
|
||||
continue
|
||||
}
|
||||
t.Logf("Goroutine %d", g.ID)
|
||||
t.Logf("Goroutine %d %#v", g.ID, g.Thread)
|
||||
logStacktrace(t, p.BinInfo(), frames)
|
||||
for i := range frames {
|
||||
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 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)
|
||||
}
|
||||
|
@ -271,8 +271,8 @@ func TestIssue411(t *testing.T) {
|
||||
}
|
||||
test.AllowRecording(t)
|
||||
withTestTerminal("math", t, func(term *FakeTerminal) {
|
||||
term.MustExec("break math.go:8")
|
||||
term.MustExec("trace math.go:9")
|
||||
term.MustExec("break _fixtures/math.go:8")
|
||||
term.MustExec("trace _fixtures/math.go:9")
|
||||
term.MustExec("continue")
|
||||
out := term.MustExec("next")
|
||||
if !strings.HasPrefix(out, "> main.main()") {
|
||||
@ -683,7 +683,9 @@ func TestCheckpoints(t *testing.T) {
|
||||
term.MustExec("checkpoints")
|
||||
listIsAt(t, term, "next", 17, -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"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"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:
|
||||
|
||||
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 -m pie # only on linux
|
||||
@ -295,7 +296,7 @@ func testCmd(cmd *cobra.Command, args []string) {
|
||||
|
||||
fmt.Println("Testing default backend")
|
||||
testCmdIntl("all", "", "default", "normal")
|
||||
if inpath("lldb-server") {
|
||||
if inpath("lldb-server") && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
||||
fmt.Println("\nTesting LLDB backend")
|
||||
testCmdIntl("basic", "", "lldb", "normal")
|
||||
}
|
||||
|
@ -1205,7 +1205,6 @@ func TestCallFunction(t *testing.T) {
|
||||
// support calling optimized functions
|
||||
{`strings.Join(nil, "")`, []string{`:string:""`}, 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(stringslice, ",")`, []string{`:string:"one,two,three"`}, 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
|
||||
}
|
||||
|
||||
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) {
|
||||
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", 0)
|
||||
if err != nil {
|
||||
@ -1244,6 +1251,11 @@ func TestCallFunction(t *testing.T) {
|
||||
for _, tc := range testcases112 {
|
||||
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) {
|
||||
@ -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!!!
|
||||
testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user