diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 3e87e08a..74c888c9 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -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 } diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index b69c502e..dd6fbb78 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -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) diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go index dd3b5f58..b5813cd0 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -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) { diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index 6e9e79eb..3a36d183 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -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) diff --git a/pkg/goversion/compat.go b/pkg/goversion/compat.go index d0bcac4b..0a063d73 100644 --- a/pkg/goversion/compat.go +++ b/pkg/goversion/compat.go @@ -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) ) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 73a5d452..47451324 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -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 != "" { diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 7ebf751c..56485003 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -216,7 +216,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e return nil, err } - return proc.NewTarget(p), nil + return proc.NewTarget(p, false), nil } // initialize for core files doesn't do much diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 1a0f6676..f73d7691 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -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 { diff --git a/pkg/proc/gdbserial/gdbserver_conn.go b/pkg/proc/gdbserial/gdbserver_conn.go index a0921615..453b4bef 100644 --- a/pkg/proc/gdbserial/gdbserver_conn.go +++ b/pkg/proc/gdbserial/gdbserver_conn.go @@ -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 diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index dc437fcd..ff5afec9 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -96,7 +96,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) return nil, err } - return proc.NewTarget(p), nil + return proc.NewTarget(p, false), nil } // ErrPerfEventParanoid is the error returned by Reply and Record if diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index 41407c8a..9e00df1a 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -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) diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index cbaf91e2..41a1eaca 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -126,7 +126,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, return nil, err } - return proc.NewTarget(dbp), err + return proc.NewTarget(dbp, false), err } // Attach to an existing process with the given PID. @@ -158,7 +158,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) { dbp.Detach(false) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } // Kill kills the process. diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index ad7d226a..f24d6004 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -87,7 +87,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (* if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } // Attach to an existing process with the given PID. Once attached, if @@ -111,7 +111,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { dbp.Detach(false) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } func initialize(dbp *Process) error { diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index a45ec855..855d399e 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -92,7 +92,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (* if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } // Attach to an existing process with the given PID. Once attached, if @@ -123,7 +123,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { if err != nil { return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, false), nil } func initialize(dbp *Process) error { diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 5a763bcc..6c36443a 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "syscall" "unsafe" @@ -55,6 +56,15 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, } closer.Close() + env := os.Environ() + for i := range env { + if strings.HasPrefix(env[i], "GODEBUG=") { + // Go 1.14 asynchronous preemption mechanism is incompatible with + // debuggers, see: https://github.com/golang/go/issues/36494 + env[i] += ",asyncpreemptoff=1" + } + } + var p *os.Process dbp := New(0) dbp.execPtraceFunc(func() { @@ -64,6 +74,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, Sys: &syscall.SysProcAttr{ CreationFlags: _DEBUG_ONLY_THIS_PROCESS, }, + Env: env, } p, err = os.StartProcess(argv0Go, cmd, attr) }) @@ -79,7 +90,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, dbp.Detach(true) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, true), nil } func initialize(dbp *Process) error { @@ -168,7 +179,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) { dbp.Detach(true) return nil, err } - return proc.NewTarget(dbp), nil + return proc.NewTarget(dbp, true), nil } // kill kills the process. @@ -472,6 +483,8 @@ func (dbp *Process) stop(trapthread *Thread) (err error) { func (dbp *Process) detach(kill bool) error { if !kill { + //TODO(aarzilli): when debug.Target exist Detach should be moved to + // debug.Target and the call to RestoreAsyncPreempt should be moved there. for _, thread := range dbp.threads { _, err := _ResumeThread(thread.os.hThread) if err != nil { diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go index 899962ba..2d465f8e 100644 --- a/pkg/proc/native/threads_windows.go +++ b/pkg/proc/native/threads_windows.go @@ -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() { diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index b4258310..bcabab84 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -5,9 +5,12 @@ import ( "errors" "fmt" "go/ast" + "go/constant" "go/token" "path/filepath" "strconv" + + "github.com/go-delve/delve/pkg/goversion" ) // ErrNotExecutable is returned after attempting to execute a non-executable file @@ -814,3 +817,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) +} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 7edc0b89..5ce4bba5 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -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" { diff --git a/pkg/proc/target.go b/pkg/proc/target.go index a2b46de5..e97b117c 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -7,6 +7,9 @@ type Target struct { // fncallForG stores a mapping of current active function calls. fncallForG map[int]*callInjection + asyncPreemptChanged bool // runtime/debug.asyncpreemptoff was changed + asyncPreemptOff int64 // cached value of runtime/debug.asyncpreemptoff + // gcache is a cache for Goroutines that we // have read and parsed from the targets memory. // This must be cleared whenever the target is resumed. @@ -14,12 +17,17 @@ type Target struct { } // NewTarget returns an initialized Target object. -func NewTarget(p Process) *Target { +func NewTarget(p Process, disableAsyncPreempt bool) *Target { t := &Target{ Process: p, fncallForG: make(map[int]*callInjection), } t.gcache.init(p.BinInfo()) + + if disableAsyncPreempt { + setAsyncPreemptOff(t, 1) + } + return t } @@ -45,3 +53,10 @@ func (t *Target) Restart(from string) error { t.ClearAllGCache() return t.Process.Restart(from) } + +func (t *Target) Detach(kill bool) error { + if !kill && t.asyncPreemptChanged { + setAsyncPreemptOff(t, t.asyncPreemptOff) + } + return t.Process.Detach(kill) +} diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 911c7c7d..0c1df4ab 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -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) }) } diff --git a/scripts/make.go b/scripts/make.go index dc077647..b4ddde0b 100644 --- a/scripts/make.go +++ b/scripts/make.go @@ -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") } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 347b8ecc..5a7a3e8e 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -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}) })