diff --git a/_scripts/make.go b/_scripts/make.go index 3b8983d3..9e3ef3ef 100644 --- a/_scripts/make.go +++ b/_scripts/make.go @@ -353,10 +353,30 @@ func testStandard() { fmt.Println("\nTesting RR backend") testCmdIntl("basic", "", "rr", "normal") } - if TestIncludePIE && (runtime.GOOS == "linux" || (runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15))) { - fmt.Println("\nTesting PIE buildmode, default backend") - testCmdIntl("basic", "", "default", "pie") - testCmdIntl("core", "", "default", "pie") + if TestIncludePIE { + dopie := false + switch runtime.GOOS { + case "linux": + dopie = true + case "windows": + // only on Go 1.15 or later, with CGO_ENABLED and gcc found in path + if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { + out, err := exec.Command("go", "env", "CGO_ENABLED").CombinedOutput() + if err != nil { + panic(err) + } + if strings.TrimSpace(string(out)) == "1" { + if _, err = exec.LookPath("gcc"); err == nil { + dopie = true + } + } + } + } + if dopie { + fmt.Println("\nTesting PIE buildmode, default backend") + testCmdIntl("basic", "", "default", "pie") + testCmdIntl("core", "", "default", "pie") + } } if runtime.GOOS == "linux" && inpath("rr") { fmt.Println("\nTesting PIE buildmode, RR backend") diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index 7962c4d2..f21bb222 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -183,19 +183,18 @@ type returnBreakpointInfo struct { } // CheckCondition evaluates bp's condition on thread. -func (bp *Breakpoint) CheckCondition(thread Thread) BreakpointState { - bpstate := BreakpointState{Breakpoint: bp, Active: false, Stepping: false, SteppingInto: false, CondError: nil} +func (bp *Breakpoint) checkCondition(tgt *Target, thread Thread, bpstate *BreakpointState) { + *bpstate = BreakpointState{Breakpoint: bp, Active: false, Stepping: false, SteppingInto: false, CondError: nil} for _, breaklet := range bp.Breaklets { - bpstate.checkCond(breaklet, thread) + bpstate.checkCond(tgt, breaklet, thread) } - return bpstate } -func (bpstate *BreakpointState) checkCond(breaklet *Breaklet, thread Thread) { +func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, thread Thread) { var condErr error active := true if breaklet.Cond != nil { - active, condErr = evalBreakpointCondition(thread, breaklet.Cond) + active, condErr = evalBreakpointCondition(tgt, thread, breaklet.Cond) } if condErr != nil && bpstate.CondError == nil { @@ -334,13 +333,13 @@ func (bp *Breakpoint) UserBreaklet() *Breaklet { return nil } -func evalBreakpointCondition(thread Thread, cond ast.Expr) (bool, error) { +func evalBreakpointCondition(tgt *Target, thread Thread, cond ast.Expr) (bool, error) { if cond == nil { return true, nil } - scope, err := GoroutineScope(nil, thread) + scope, err := GoroutineScope(tgt, thread) if err != nil { - scope, err = ThreadScope(nil, thread) + scope, err = ThreadScope(tgt, thread) if err != nil { return true, err } diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index cdd0f282..4c39dab5 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -297,7 +297,7 @@ func TestCore(t *testing.T) { if mainFrame == nil { t.Fatalf("Couldn't find main in stack %v", panickingStack) } - msg, err := proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64}) + msg, err := proc.FrameToScope(p, p.Memory(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64}) if err != nil { t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err) } @@ -427,7 +427,7 @@ mainSearch: t.Fatal("could not find main.main frame") } - scope := proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, *mainFrame) + scope := proc.FrameToScope(p, p.Memory(), nil, *mainFrame) loadConfig := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1} v1, err := scope.EvalVariable("t", loadConfig) assertNoError(err, t, "EvalVariable(t)") diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index d4f993d8..dd935a04 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -101,7 +101,7 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error return d.EvalScope(dbp, ct) } - return FrameToScope(dbp, dbp.BinInfo(), dbp.Memory(), g, locs[frame:]...), nil + return FrameToScope(dbp, dbp.Memory(), g, locs[frame:]...), nil } // FrameToScope returns a new EvalScope for frames[0]. @@ -109,7 +109,7 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error // frames[0].Regs.SP() and frames[1].Regs.CFA will be cached. // Otherwise all memory between frames[0].Regs.SP() and frames[0].Regs.CFA // will be cached. -func FrameToScope(t *Target, bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope { +func FrameToScope(t *Target, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope { // Creates a cacheMem that will preload the entire stack frame the first // time any local variable is read. // Remember that the stack grows downward in memory. @@ -124,7 +124,7 @@ func FrameToScope(t *Target, bi *BinaryInfo, thread MemoryReadWriter, g *G, fram thread = cacheMemory(thread, minaddr, int(maxaddr-minaddr)) } - s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, g: g, BinInfo: bi, target: t, frameOffset: frames[0].FrameOffset()} + s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, g: g, BinInfo: t.BinInfo(), target: t, frameOffset: frames[0].FrameOffset()} s.PC = frames[0].lastpc return s } @@ -138,7 +138,7 @@ func ThreadScope(t *Target, thread Thread) (*EvalScope, error) { if len(locations) < 1 { return nil, errors.New("could not decode first frame") } - return FrameToScope(t, thread.BinInfo(), thread.ProcessMemory(), nil, locations...), nil + return FrameToScope(t, thread.ProcessMemory(), nil, locations...), nil } // GoroutineScope returns an EvalScope for the goroutine running on the given thread. @@ -154,7 +154,7 @@ func GoroutineScope(t *Target, thread Thread) (*EvalScope, error) { if err != nil { return nil, err } - return FrameToScope(t, thread.BinInfo(), thread.ProcessMemory(), g, locations...), nil + return FrameToScope(t, thread.ProcessMemory(), g, locations...), nil } // EvalExpression returns the value of the given expression. diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 62ebb371..4d6a3c2a 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -1806,7 +1806,7 @@ func (t *gdbThread) SetCurrentBreakpoint(adjustPC bool) error { return err } } - t.CurrentBreakpoint = bp.CheckCondition(t) + t.CurrentBreakpoint.Breakpoint = bp } return nil } diff --git a/pkg/proc/moduledata.go b/pkg/proc/moduledata.go index 3eb28a2e..35a4cac3 100644 --- a/pkg/proc/moduledata.go +++ b/pkg/proc/moduledata.go @@ -13,7 +13,7 @@ type moduleData struct { } func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) ([]moduleData, error) { - scope := globalScope(bi, bi.Images[0], mem) + scope := globalScope(nil, bi, bi.Images[0], mem) var md *Variable md, err := scope.findGlobal("runtime", "firstmoduledata") if err != nil { @@ -130,7 +130,7 @@ func resolveNameOff(bi *BinaryInfo, mds []moduleData, typeAddr, off uint64, mem } func reflectOffsMapAccess(bi *BinaryInfo, off uint64, mem MemoryReadWriter) (*Variable, error) { - scope := globalScope(bi, bi.Images[0], mem) + scope := globalScope(nil, bi, bi.Images[0], mem) reflectOffs, err := scope.findGlobal("runtime", "reflectOffs") if err != nil { return nil, err diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 09202e92..19b2e25c 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -11,6 +11,7 @@ import ( "github.com/go-delve/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc/internal/ebpf" + "github.com/go-delve/delve/pkg/proc/winutil" ) // osProcessDetails holds Windows specific information. @@ -437,11 +438,17 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) return nil, err } + context := winutil.NewCONTEXT() + for _, thread := range dbp.threads { + thread.os.delayErr = nil if !thread.os.dbgUiRemoteBreakIn { - _, err := _SuspendThread(thread.os.hThread) - if err != nil { - return nil, err + // Wait before reporting the error, the thread could be removed when we + // call waitForDebugEvent in the next loop. + _, thread.os.delayErr = _SuspendThread(thread.os.hThread) + if thread.os.delayErr == nil { + // This call will block until the thread has stopped. + _ = _GetThreadContext(thread.os.hThread, context) } } } @@ -467,6 +474,31 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) } } + // Check if trapthread still exist, if the process is dying it could have + // been removed while we were stopping the other threads. + trapthreadFound := false + for _, thread := range dbp.threads { + if thread.ID == trapthread.ID { + trapthreadFound = true + } + if thread.os.delayErr != nil && thread.os.delayErr != syscall.Errno(0x5) { + // Do not report Access is denied error, it is caused by the thread + // having already died but we haven't been notified about it yet. + return nil, thread.os.delayErr + } + } + + if !trapthreadFound { + // trapthread exited during stop, pick another one + trapthread = nil + for _, thread := range dbp.threads { + if thread.CurrentBreakpoint.Breakpoint != nil && thread.os.delayErr == nil { + trapthread = thread + break + } + } + } + return trapthread, nil } diff --git a/pkg/proc/native/threads.go b/pkg/proc/native/threads.go index 5ae8abc0..3088545e 100644 --- a/pkg/proc/native/threads.go +++ b/pkg/proc/native/threads.go @@ -153,9 +153,7 @@ func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error { } } - if bp != nil { - t.CurrentBreakpoint = bp.CheckCondition(t) - } + t.CurrentBreakpoint.Breakpoint = bp return nil } diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go index 030a987b..ff07eea3 100644 --- a/pkg/proc/native/threads_windows.go +++ b/pkg/proc/native/threads_windows.go @@ -18,6 +18,7 @@ type waitStatus sys.WaitStatus type osSpecificDetails struct { hThread syscall.Handle dbgUiRemoteBreakIn bool // whether thread is an auxiliary DbgUiRemoteBreakIn thread created by Windows + delayErr error } func (t *nativeThread) singleStep() error { diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 74bf01e9..93702530 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -1149,7 +1149,7 @@ func evalVariableOrError(p *proc.Target, symbol string) (*proc.Variable, error) var frame proc.Stackframe frame, err = findFirstNonRuntimeFrame(p) if err == nil { - scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame) + scope = proc.FrameToScope(p, p.Memory(), nil, frame) } } else { scope, err = proc.GoroutineScope(p, p.CurrentThread()) @@ -3036,7 +3036,7 @@ func TestIssue871(t *testing.T) { var frame proc.Stackframe frame, err = findFirstNonRuntimeFrame(p) if err == nil { - scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame) + scope = proc.FrameToScope(p, p.Memory(), nil, frame) } } else { scope, err = proc.GoroutineScope(p, p.CurrentThread()) @@ -3482,7 +3482,7 @@ func TestIssue1034(t *testing.T) { assertNoError(p.Continue(), t, "Continue()") frames, err := p.SelectedGoroutine().Stacktrace(10, 0) assertNoError(err, t, "Stacktrace") - scope := proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frames[2:]...) + scope := proc.FrameToScope(p, p.Memory(), nil, frames[2:]...) args, _ := scope.FunctionArguments(normalLoadConfig) assertNoError(err, t, "FunctionArguments()") if len(args) > 0 { @@ -3613,9 +3613,13 @@ func TestIssue1101(t *testing.T) { exitErr = p.Continue() } if pexit, exited := exitErr.(proc.ErrProcessExited); exited { - if pexit.Status != 2 && testBackend != "lldb" { - // looks like there's a bug with debugserver on macOS that sometimes + if pexit.Status != 2 && testBackend != "lldb" && (runtime.GOOS != "linux" || runtime.GOARCH != "386") { + // Looks like there's a bug with debugserver on macOS that sometimes // will report exit status 0 instead of the proper exit status. + // + // Also it seems that sometimes on linux/386 we will not receive the + // exit status. This happens if the process exits at the same time as it + // receives a signal. t.Fatalf("process exited status %d (expected 2)", pexit.Status) } } else { @@ -5141,8 +5145,8 @@ func TestDump(t *testing.T) { t.Errorf("Frame mismatch %d.%d\nlive:\t%s\ncore:\t%s", gos[i].ID, j, convertFrame(p.BinInfo().Arch, &frames[j]), convertFrame(p.BinInfo().Arch, &cframes[j])) } if frames[j].Call.Fn != nil && frames[j].Call.Fn.Name == "main.main" { - scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), gos[i], frames[j:]...) - cscope = proc.FrameToScope(c, c.BinInfo(), c.Memory(), cgos[i], cframes[j:]...) + scope = proc.FrameToScope(p, p.Memory(), gos[i], frames[j:]...) + cscope = proc.FrameToScope(c, c.Memory(), cgos[i], cframes[j:]...) } } } @@ -5263,6 +5267,7 @@ func TestCompositeMemoryWrite(t *testing.T) { } func TestVariablesWithExternalLinking(t *testing.T) { + protest.MustHaveCgo(t) // Tests that macOSDebugFrameBugWorkaround works. // See: // https://github.com/golang/go/issues/25841 diff --git a/pkg/proc/target.go b/pkg/proc/target.go index bb52fdd2..8eb3f166 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -203,7 +203,7 @@ func (t *Target) IsCgo() bool { if t.iscgo != nil { return *t.iscgo } - scope := globalScope(t.BinInfo(), t.BinInfo().Images[0], t.Memory()) + scope := globalScope(t, t.BinInfo(), t.BinInfo().Images[0], t.Memory()) iscgov, err := scope.findGlobal("runtime", "iscgo") if err == nil { iscgov.loadValue(loadFullValue) @@ -333,7 +333,7 @@ func setAsyncPreemptOff(p *Target, v int64) { return } logger := p.BinInfo().logger - scope := globalScope(p.BinInfo(), p.BinInfo().Images[0], p.Memory()) + scope := globalScope(p, p.BinInfo(), p.BinInfo().Images[0], p.Memory()) 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) diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index 0c556e38..70302d6f 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -70,9 +70,17 @@ func (dbp *Target) Continue() error { return nil } dbp.ClearCaches() - trapthread, stopReason, err := dbp.proc.ContinueOnce() + trapthread, stopReason, contOnceErr := dbp.proc.ContinueOnce() dbp.StopReason = stopReason - if err != nil { + + threads := dbp.ThreadList() + for _, thread := range threads { + if thread.Breakpoint().Breakpoint != nil { + thread.Breakpoint().Breakpoint.checkCondition(dbp, thread, thread.Breakpoint()) + } + } + + if contOnceErr != nil { // Attempt to refresh status of current thread/current goroutine, see // Issue #2078. // Errors are ignored because depending on why ContinueOnce failed this @@ -84,17 +92,15 @@ func (dbp *Target) Continue() error { dbp.selectedGoroutine, _ = GetG(curth) } } - if pe, ok := err.(ErrProcessExited); ok { + if pe, ok := contOnceErr.(ErrProcessExited); ok { dbp.exitStatus = pe.Status } - return err + return contOnceErr } if dbp.StopReason == StopLaunched { dbp.ClearSteppingBreakpoints() } - threads := dbp.ThreadList() - callInjectionDone, callErr := callInjectionProtocol(dbp, threads) // callErr check delayed until after pickCurrentThread, which must always // happen, otherwise the debugger could be left in an inconsistent @@ -190,7 +196,7 @@ func (dbp *Target) Continue() error { return conditionErrors(threads) } case curbp.Active: - onNextGoroutine, err := onNextGoroutine(curthread, dbp.Breakpoints()) + onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints()) if err != nil { return err } @@ -1015,7 +1021,7 @@ func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr } // onNextGoroutine returns true if this thread is on the goroutine requested by the current 'next' command -func onNextGoroutine(thread Thread, breakpoints *BreakpointMap) (bool, error) { +func onNextGoroutine(tgt *Target, thread Thread, breakpoints *BreakpointMap) (bool, error) { var breaklet *Breaklet breakletSearch: for i := range breakpoints.M { @@ -1038,12 +1044,13 @@ breakletSearch: // function or by returning from the function: // runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z) // Here we are only interested in testing the runtime.curg.goid clause. - w := onNextGoroutineWalker{thread: thread} + w := onNextGoroutineWalker{tgt: tgt, thread: thread} ast.Walk(&w, breaklet.Cond) return w.ret, w.err } type onNextGoroutineWalker struct { + tgt *Target thread Thread ret bool err error @@ -1051,7 +1058,7 @@ type onNextGoroutineWalker struct { func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor { if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL && exprToString(binx.X) == "runtime.curg.goid" { - w.ret, w.err = evalBreakpointCondition(w.thread, n.(ast.Expr)) + w.ret, w.err = evalBreakpointCondition(w.tgt, w.thread, n.(ast.Expr)) return nil } return w diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 974828ee..e6b762d0 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -581,8 +581,8 @@ func (err *IsNilErr) Error() string { return fmt.Sprintf("%s is nil", err.name) } -func globalScope(bi *BinaryInfo, image *Image, mem MemoryReadWriter) *EvalScope { - return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: image.StaticBase}, Mem: mem, g: nil, BinInfo: bi, frameOffset: 0} +func globalScope(tgt *Target, bi *BinaryInfo, image *Image, mem MemoryReadWriter) *EvalScope { + return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: image.StaticBase}, Mem: mem, g: nil, BinInfo: bi, target: tgt, frameOffset: 0} } func newVariableFromThread(t Thread, name string, addr uint64, dwarfType godwarf.Type) *Variable { @@ -947,8 +947,8 @@ func (v *Variable) fieldVariable(name string) *Variable { var errTracebackAncestorsDisabled = errors.New("tracebackancestors is disabled") // Ancestors returns the list of ancestors for g. -func Ancestors(p Process, g *G, n int) ([]Ancestor, error) { - scope := globalScope(p.BinInfo(), p.BinInfo().Images[0], p.Memory()) +func Ancestors(p *Target, g *G, n int) ([]Ancestor, error) { + scope := globalScope(p, p.BinInfo(), p.BinInfo().Images[0], p.Memory()) tbav, err := scope.EvalExpression("runtime.debug.tracebackancestors", loadSingleValue) if err == nil && tbav.Unreadable == nil && tbav.Kind == reflect.Int { tba, _ := constant.Int64Val(tbav.Value) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 1402f7a8..df93a7b0 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -1752,7 +1752,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo } if cfg != nil && rawlocs[i].Current.Fn != nil { var err error - scope := proc.FrameToScope(d.target, d.target.BinInfo(), d.target.Memory(), nil, rawlocs[i:]...) + scope := proc.FrameToScope(d.target, d.target.Memory(), nil, rawlocs[i:]...) locals, err := scope.LocalVariables(*cfg) if err != nil { return nil, err diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 1acbbacd..95a2c8e1 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -92,7 +92,7 @@ func evalScope(p *proc.Target) (*proc.EvalScope, error) { if err != nil { return nil, err } - return proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame), nil + return proc.FrameToScope(p, p.Memory(), nil, frame), nil } func evalVariable(p *proc.Target, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { @@ -471,7 +471,7 @@ func TestLocalVariables(t *testing.T) { var frame proc.Stackframe frame, err = findFirstNonRuntimeFrame(p) if err == nil { - scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame) + scope = proc.FrameToScope(p, p.Memory(), nil, frame) } } else { scope, err = proc.GoroutineScope(p, p.CurrentThread())