Repository: go-delve/delve
Pull request #2225 (2020-11-11 12:32:14)

Travis-CI: hold back version of Go 1.15.x to 1.15.3



aarzilli: There is a problem with the debug_lines section generated by Go 1.15.4, see https://github.com/golang/go/issues/42484, I propose that for now we keep our CI on 1.15.3. In practice this seems to only affect linux/arm64 (at least on our tests, I think it can affect amd64 binaries as well) but I couldn't figure out how to just have arm64 on 1.15.3 and I don't think it's a big deal.

If my fix for Go is approved and backported we'll just reenable 1.15.x, if it isn't I have [a workaround for delve](https://github.com/go-delve/delve/pull/2224) that passes tests on 1.15.4 that's not very elegant.

Patch:
diff --git a/.travis.yml b/.travis.yml
index c269c384..47fbf7b1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ arch:
   - arm64
 
 go:
-  - 1.15.x
+  - 1.15.3 # hold back version of Go, see issue #42484
   - 1.14.x
   - 1.13.x
   - tip
diff --git a/_fixtures/fncall.go b/_fixtures/fncall.go
index 6824fcf8..9937e530 100644
--- a/_fixtures/fncall.go
+++ b/_fixtures/fncall.go
@@ -2,10 +2,13 @@ package main
 
 import (
 	"fmt"
+	"os"
 	"runtime"
 	"strings"
 )
 
+var call = "this is a variable named `call`"
+
 func callstacktrace() (stacktrace string) {
 	for skip := 0; ; skip++ {
 		pc, file, line, ok := runtime.Caller(skip)
@@ -18,16 +21,35 @@ func callstacktrace() (stacktrace string) {
 	return stacktrace
 }
 
+func call0(a, b int) {
+	fmt.Printf("call0: first: %d second: %d\n", a, b)
+}
+
 func call1(a, b int) int {
 	fmt.Printf("first: %d second: %d\n", a, b)
 	return a + b
 }
 
+func call2(a, b int) (int, int) {
+	fmt.Printf("call2: first: %d second: %d\n", a, b)
+	return a, b
+}
+
+func callexit() {
+	fmt.Printf("about to exit\n")
+	os.Exit(0)
+}
+
 func callpanic() {
 	fmt.Printf("about to panic\n")
 	panic("callpanic panicked")
 }
 
+func callbreak() {
+	fmt.Printf("about to break")
+	runtime.Breakpoint()
+}
+
 func stringsJoin(v []string, sep string) string {
 	// This is needed because strings.Join is in an optimized package and
 	// because of a bug in the compiler arguments of optimized functions don't
@@ -178,5 +200,5 @@ func main() {
 	d.Method()
 	d.Base.Method()
 	x.CallMe()
-	fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5))
+	fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5))
 }
diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go
index f785480f..90632790 100644
--- a/pkg/proc/interface.go
+++ b/pkg/proc/interface.go
@@ -98,6 +98,7 @@ type ThreadInfo interface {
 
 // ProcessManipulation is an interface for changing the execution state of a process.
 type ProcessManipulation interface {
+	// RequestManualStop attempts to stop all the process' threads.
 	RequestManualStop() error
 	// CheckAndClearManualStopRequest returns true the first time it's called
 	// after a call to RequestManualStop.
diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go
index 6509bf54..4dbaf11d 100644
--- a/pkg/proc/native/proc.go
+++ b/pkg/proc/native/proc.go
@@ -27,11 +27,14 @@ type nativeProcess struct {
 
 	os                  *osProcessDetails
 	firstStart          bool
-	stopMu              sync.Mutex
 	resumeChan          chan<- struct{}
 	ptraceChan          chan func()
 	ptraceDoneChan      chan interface{}
 	childProcess        bool // this process was launched, not attached to
+	stopMu              sync.Mutex // protects manualStopRequested
+	// manualStopRequested is set if all the threads in the process were
+	// signalled to stop as a result of a Halt API call. Used to disambiguate
+	// why a thread is found to have stopped.
 	manualStopRequested bool
 
 	// Controlling terminal file descriptor for
@@ -176,7 +179,7 @@ func (dbp *nativeProcess) Breakpoints() *proc.BreakpointMap {
 	return &dbp.breakpoints
 }
 
-// RequestManualStop sets the `halt` flag and
+// RequestManualStop sets the `manualStopRequested` flag and
 // sends SIGSTOP to all threads.
 func (dbp *nativeProcess) RequestManualStop() error {
 	if dbp.exited {
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index 59b91fe7..bef040e1 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -76,6 +76,32 @@ func (pe ErrProcessExited) Error() string {
 // case only one will be reported.
 type StopReason uint8
 
+// String maps StopReason to string representation.
+func (sr StopReason) String() string {
+	switch sr {
+	case StopUnknown:
+		return "unkown"
+	case StopLaunched:
+		return "launched"
+	case StopAttached:
+		return "attached"
+	case StopExited:
+		return "exited"
+	case StopBreakpoint:
+		return "breakpoint"
+	case StopHardcodedBreakpoint:
+		return "hardcoded breakpoint"
+	case StopManual:
+		return "manual"
+	case StopNextFinished:
+		return "next finished"
+	case StopCallReturned:
+		return "call returned"
+	default:
+		return ""
+	}
+}
+
 const (
 	StopUnknown             StopReason = iota
 	StopLaunched                       // The process was just launched
diff --git a/service/dap/error_ids.go b/service/dap/error_ids.go
index c75b4d42..ddb88c83 100644
--- a/service/dap/error_ids.go
+++ b/service/dap/error_ids.go
@@ -10,14 +10,15 @@ const (
 
 	// Where applicable and for consistency only,
 	// values below are inspired the original vscode-go debug adaptor.
-	FailedToLaunch            = 3000
-	FailedtoAttach            = 3001
-	UnableToSetBreakpoints    = 2002
-	UnableToDisplayThreads    = 2003
-	UnableToProduceStackTrace = 2004
-	UnableToListLocals        = 2005
-	UnableToListArgs          = 2006
-	UnableToListGlobals       = 2007
-	UnableToLookupVariable    = 2008
+	FailedToLaunch             = 3000
+	FailedtoAttach             = 3001
+	UnableToSetBreakpoints     = 2002
+	UnableToDisplayThreads     = 2003
+	UnableToProduceStackTrace  = 2004
+	UnableToListLocals         = 2005
+	UnableToListArgs           = 2006
+	UnableToListGlobals        = 2007
+	UnableToLookupVariable     = 2008
+	UnableToEvaluateExpression = 2009
 	// Add more codes as we support more requests
 )
diff --git a/service/dap/server.go b/service/dap/server.go
index 6557a6c1..b7f1ba44 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -18,6 +18,7 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"strings"
 
 	"github.com/go-delve/delve/pkg/gobuild"
@@ -58,8 +59,10 @@ type Server struct {
 	// binaryToRemove is the compiled binary to be removed on disconnect.
 	binaryToRemove string
 	// stackFrameHandles maps frames of each goroutine to unique ids across all goroutines.
+	// Reset at every stop.
 	stackFrameHandles *handlesMap
 	// variableHandles maps compound variables to unique references within their stack frame.
+	// Reset at every stop.
 	// See also comment for convertVariable.
 	variableHandles *variablesHandlesMap
 	// args tracks special settings for handling debug session requests.
@@ -317,8 +320,7 @@ func (s *Server) handleRequest(request dap.Message) {
 		// Optional (capability ‘supportsTerminateThreadsRequest’)
 		s.sendUnsupportedErrorResponse(request.Request)
 	case *dap.EvaluateRequest:
-		// Required - TODO
-		// TODO: implement this request in V0
+		// Required
 		s.onEvaluateRequest(request)
 	case *dap.StepInTargetsRequest:
 		// Optional (capability ‘supportsStepInTargetsRequest’)
@@ -673,7 +675,7 @@ func (s *Server) onAttachRequest(request *dap.AttachRequest) { // TODO V0
 // onNextRequest handles 'next' request.
 // This is a mandatory request to support.
 func (s *Server) onNextRequest(request *dap.NextRequest) {
-	// This ingores threadId argument to match the original vscode-go implementation.
+	// This ignores threadId argument to match the original vscode-go implementation.
 	// TODO(polina): use SwitchGoroutine to change the current goroutine.
 	s.send(&dap.NextResponse{Response: *newResponse(request.Request)})
 	s.doCommand(api.Next)
@@ -682,7 +684,7 @@ func (s *Server) onNextRequest(request *dap.NextRequest) {
 // onStepInRequest handles 'stepIn' request
 // This is a mandatory request to support.
 func (s *Server) onStepInRequest(request *dap.StepInRequest) {
-	// This ingores threadId argument to match the original vscode-go implementation.
+	// This ignores threadId argument to match the original vscode-go implementation.
 	// TODO(polina): use SwitchGoroutine to change the current goroutine.
 	s.send(&dap.StepInResponse{Response: *newResponse(request.Request)})
 	s.doCommand(api.Step)
@@ -691,7 +693,7 @@ func (s *Server) onStepInRequest(request *dap.StepInRequest) {
 // onStepOutRequest handles 'stepOut' request
 // This is a mandatory request to support.
 func (s *Server) onStepOutRequest(request *dap.StepOutRequest) {
-	// This ingores threadId argument to match the original vscode-go implementation.
+	// This ignores threadId argument to match the original vscode-go implementation.
 	// TODO(polina): use SwitchGoroutine to change the current goroutine.
 	s.send(&dap.StepOutResponse{Response: *newResponse(request.Request)})
 	s.doCommand(api.StepOut)
@@ -913,15 +915,32 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
 	s.send(response)
 }
 
-// convertVariable converts api.Variable to dap.Variable value and reference.
+// convertVariable converts proc.Variable to dap.Variable value and reference.
 // Variable reference is used to keep track of the children associated with each
-// variable. It is shared with the host via a scopes response and is an index to
-// the s.variableHandles map, so it can be referenced from a subsequent variables
-// request. A positive reference signals the host that another variables request
-// can be issued to get the elements of the compound variable. As a custom, a zero
-// reference, reminiscent of a zero pointer, is used to indicate that a scalar
-// variable cannot be "dereferenced" to get its elements (as there are none).
+// variable. It is shared with the host via scopes or evaluate response and is an index
+// into the s.variableHandles map, used to look up variables and their children on
+// subsequent variables requests. A positive reference signals the host that another
+// variables request can be issued to get the elements of the compound variable. As a
+// custom, a zero reference, reminiscent of a zero pointer, is used to indicate that
+// a scalar variable cannot be "dereferenced" to get its elements (as there are none).
 func (s *Server) convertVariable(v *proc.Variable) (value string, variablesReference int) {
+	return s.convertVariableWithOpts(v, false)
+}
+
+func (s *Server) convertVariableToString(v *proc.Variable) string {
+	val, _ := s.convertVariableWithOpts(v, true)
+	return val
+}
+
+// convertVarialbeWithOpts allows to skip reference generation in case all we need is
+// a string representation of the variable.
+func (s *Server) convertVariableWithOpts(v *proc.Variable, skipRef bool) (value string, variablesReference int) {
+	maybeCreateVariableHandle := func(v *proc.Variable) int {
+		if skipRef {
+			return 0
+		}
+		return s.variableHandles.create(v)
+	}
 	if v.Unreadable != nil {
 		value = fmt.Sprintf("unreadable <%v>", v.Unreadable)
 		return
@@ -943,12 +962,12 @@ func (s *Server) convertVariable(v *proc.Variable) (value string, variablesRefer
 			value = "void"
 		} else {
 			value = fmt.Sprintf("<%s>(%#x)", typeName, v.Children[0].Addr)
-			variablesReference = s.variableHandles.create(v)
+			variablesReference = maybeCreateVariableHandle(v)
 		}
 	case reflect.Array:
 		value = "<" + typeName + ">"
 		if len(v.Children) > 0 {
-			variablesReference = s.variableHandles.create(v)
+			variablesReference = maybeCreateVariableHandle(v)
 		}
 	case reflect.Slice:
 		if v.Base == 0 {
@@ -956,7 +975,7 @@ func (s *Server) convertVariable(v *proc.Variable) (value string, variablesRefer
 		} else {
 			value = fmt.Sprintf("<%s> (length: %d, cap: %d)", typeName, v.Len, v.Cap)
 			if len(v.Children) > 0 {
-				variablesReference = s.variableHandles.create(v)
+				variablesReference = maybeCreateVariableHandle(v)
 			}
 		}
 	case reflect.Map:
@@ -965,7 +984,7 @@ func (s *Server) convertVariable(v *proc.Variable) (value string, variablesRefer
 		} else {
 			value = fmt.Sprintf("<%s> (length: %d)", typeName, v.Len)
 			if len(v.Children) > 0 {
-				variablesReference = s.variableHandles.create(v)
+				variablesReference = maybeCreateVariableHandle(v)
 			}
 		}
 	case reflect.String:
@@ -980,11 +999,11 @@ func (s *Server) convertVariable(v *proc.Variable) (value string, variablesRefer
 			value = "nil <" + typeName + ">"
 		} else {
 			value = "<" + typeName + ">"
-			variablesReference = s.variableHandles.create(v)
+			variablesReference = maybeCreateVariableHandle(v)
 		}
 	case reflect.Interface:
 		if v.Addr == 0 {
-			// An escaped interface variable that points to nil, this shouldn't
+			// An escaped interface variable that points to nil: this shouldn't
 			// happen in normal code but can happen if the variable is out of scope,
 			// such as if an interface variable has been captured by a
 			// closure and replaced by a pointer to interface, and the pointer
@@ -1008,7 +1027,7 @@ func (s *Server) convertVariable(v *proc.Variable) (value string, variablesRefer
 			// After:
 			//   i: <interface{}(main.MyStruct)>
 			//      field1: ...
-			variablesReference = s.variableHandles.create(v)
+			variablesReference = maybeCreateVariableHandle(v)
 		}
 	case reflect.Complex64, reflect.Complex128:
 		v.Children = make([]proc.Variable, 2)
@@ -1032,16 +1051,129 @@ func (s *Server) convertVariable(v *proc.Variable) (value string, variablesRefer
 			value = "<" + typeName + ">"
 		}
 		if len(v.Children) > 0 {
-			variablesReference = s.variableHandles.create(v)
+			variablesReference = maybeCreateVariableHandle(v)
 		}
 	}
 	return
 }
 
-// onEvaluateRequest sends a not-yet-implemented error response.
+// onEvaluateRequest handles 'evalute' requests.
 // This is a mandatory request to support.
-func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) { // TODO V0
-	s.sendNotYetImplementedErrorResponse(request.Request)
+// Support the following expressions:
+// -- {expression} - evaluates the expression and returns the result as a variable
+// -- call {function} - injects a function call and returns the result as a variable
+// TODO(polina): users have complained about having to click to expand multi-level
+// variables, so consider also adding the following:
+// -- print {expression} - return the result as a string like from dlv cli
+func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
+	showErrorToUser := request.Arguments.Context != "watch"
+	if s.debugger == nil {
+		s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", "debugger is nil", showErrorToUser)
+		return
+	}
+	// Default to the topmost stack frame of the current goroutine in case
+	// no frame is specified (e.g. when stopped on entry or no call stack frame is expanded)
+	goid, frame := -1, 0
+	if sf, ok := s.stackFrameHandles.get(request.Arguments.FrameId); ok {
+		goid = sf.(stackFrame).goroutineID
+		frame = sf.(stackFrame).frameIndex
+	}
+	// TODO(polina): Support config settings via launch/attach args vs auto-loading?
+	apiCfg := &api.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
+	prcCfg := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
+
+	response := &dap.EvaluateResponse{Response: *newResponse(request.Request)}
+	isCall, err := regexp.MatchString(`^\s*call\s+\S+`, request.Arguments.Expression)
+	if err == nil && isCall { // call {expression}
+		// This call might be evaluated in the context of the frame that is not topmost
+		// if the editor is set to view the variables for one of the parent frames.
+		// If the call expression refers to any of these variables, unlike regular
+		// expressions, it will evaluate them in the context of the topmost frame,
+		// and the user will get an unexpected result or an unexpected symbol error.
+		// We prevent this but disallowing any frames other than topmost.
+		if frame > 0 {
+			s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", "call is only supported with topmost stack frame", showErrorToUser)
+			return
+		}
+		stateBeforeCall, err := s.debugger.State( /*nowait*/ true)
+		if err != nil {
+			s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
+			return
+		}
+		state, err := s.debugger.Command(&api.DebuggerCommand{
+			Name:                 api.Call,
+			ReturnInfoLoadConfig: apiCfg,
+			Expr:                 strings.Replace(request.Arguments.Expression, "call ", "", 1),
+			UnsafeCall:           false,
+			GoroutineID:          goid,
+		})
+		if _, isexited := err.(proc.ErrProcessExited); isexited || err == nil && state.Exited {
+			e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
+			s.send(e)
+			return
+		}
+		if err != nil {
+			s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
+			return
+		}
+		// After the call is done, the goroutine where we injected the call should
+		// return to the original stopped line with return values. However,
+		// it is not guaranteed to be selected due to the possibility of the
+		// of simultaenous breakpoints. Therefore, we check all threads.
+		var retVars []*proc.Variable
+		for _, t := range state.Threads {
+			if t.GoroutineID == stateBeforeCall.SelectedGoroutine.ID &&
+				t.Line == stateBeforeCall.SelectedGoroutine.CurrentLoc.Line && t.ReturnValues != nil {
+				// The call completed. Get the return values.
+				retVars, err = s.debugger.FindThreadReturnValues(t.ID, prcCfg)
+				if err != nil {
+					s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
+					return
+				}
+				break
+			}
+		}
+		if retVars == nil {
+			// The call got interrupted by a stop (e.g. breakpoint in injected
+			// function call or in another goroutine)
+			s.resetHandlesForStop()
+			stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
+			stopped.Body.AllThreadsStopped = true
+			if state.SelectedGoroutine != nil {
+				stopped.Body.ThreadId = state.SelectedGoroutine.ID
+			}
+			stopped.Body.Reason = s.debugger.StopReason().String()
+			s.send(stopped)
+			// TODO(polina): once this is asynchronous, we could wait to reply until the user
+			// continues, call ends, original stop point is hit and return values are available.
+			s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", "call stopped", showErrorToUser)
+			return
+		}
+		// The call completed and we can reply with its return values (if any)
+		if len(retVars) > 0 {
+			// Package one or more return values in a single scope-like nameless variable
+			// that preserves their names.
+			retVarsAsVar := &proc.Variable{Children: slicePtrVarToSliceVar(retVars)}
+			// As a shortcut also express the return values as a single string.
+			retVarsAsStr := ""
+			for _, v := range retVars {
+				retVarsAsStr += s.convertVariableToString(v) + ", "
+			}
+			response.Body = dap.EvaluateResponseBody{
+				Result:             strings.TrimRight(retVarsAsStr, ", "),
+				VariablesReference: s.variableHandles.create(retVarsAsVar),
+			}
+		}
+	} else { // {expression}
+		exprVar, err := s.debugger.EvalVariableInScope(goid, frame, 0, request.Arguments.Expression, prcCfg)
+		if err != nil {
+			s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
+			return
+		}
+		exprVal, exprRef := s.convertVariable(exprVar)
+		response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef}
+	}
+	s.send(response)
 }
 
 // onTerminateRequest sends a not-yet-implemented error response.
@@ -1110,7 +1242,9 @@ func (s *Server) onCancelRequest(request *dap.CancelRequest) {
 	s.sendNotYetImplementedErrorResponse(request.Request)
 }
 
-func (s *Server) sendErrorResponse(request dap.Request, id int, summary, details string) {
+// sendERrorResponseWithOpts offers configuration options.
+//   showUser - if true, the error will be shown to the user (e.g. via a visible pop-up)
+func (s *Server) sendErrorResponseWithOpts(request dap.Request, id int, summary, details string, showUser bool) {
 	er := &dap.ErrorResponse{}
 	er.Type = "response"
 	er.Command = request.Command
@@ -1119,10 +1253,16 @@ func (s *Server) sendErrorResponse(request dap.Request, id int, summary, details
 	er.Message = summary
 	er.Body.Error.Id = id
 	er.Body.Error.Format = fmt.Sprintf("%s: %s", summary, details)
+	er.Body.Error.ShowUser = showUser
 	s.log.Error(er.Body.Error.Format)
 	s.send(er)
 }
 
+// sendErrorResponse sends an error response with default visibility settings.
+func (s *Server) sendErrorResponse(request dap.Request, id int, summary, details string) {
+	s.sendErrorResponseWithOpts(request, id, summary, details, false /*showUser*/)
+}
+
 // sendInternalErrorResponse sends an "internal error" response back to the client.
 // We only take a seq here because we don't want to make assumptions about the
 // kind of message received by the server that this error is a reply to.
@@ -1173,6 +1313,11 @@ func newEvent(event string) *dap.Event {
 const BetterBadAccessError = `invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation]
 Unable to propogate EXC_BAD_ACCESS signal to target process and panic (see https://github.com/go-delve/delve/issues/852)`
 
+func (s *Server) resetHandlesForStop() {
+	s.stackFrameHandles.reset()
+	s.variableHandles.reset()
+}
+
 // doCommand runs a debugger command until it stops on
 // termination, error, breakpoint, etc, when an appropriate
 // event needs to be sent to the client.
@@ -1188,9 +1333,7 @@ func (s *Server) doCommand(command string) {
 		return
 	}
 
-	s.stackFrameHandles.reset()
-	s.variableHandles.reset()
-
+	s.resetHandlesForStop()
 	stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
 	stopped.Body.AllThreadsStopped = true
 
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index a94a20f5..6e4ced70 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -1517,6 +1517,24 @@ func (d *Debugger) Recorded() (recorded bool, tracedir string) {
 	return d.target.Recorded()
 }
 
+// FindThreadReturnValues returns the return values of the function that
+// the thread of the given 'id' just stepped out of.
+func (d *Debugger) FindThreadReturnValues(id int, cfg proc.LoadConfig) ([]*proc.Variable, error) {
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+
+	if _, err := d.target.Valid(); err != nil {
+		return nil, err
+	}
+
+	thread, found := d.target.FindThread(id)
+	if !found {
+		return nil, fmt.Errorf("could not find thread %d", id)
+	}
+
+	return thread.Common().ReturnValues(cfg), nil
+}
+
 // Checkpoint will set a checkpoint specified by the locspec.
 func (d *Debugger) Checkpoint(where string) (int, error) {
 	d.targetMutex.Lock()


Test patch:
diff --git a/_fixtures/testvariables.go b/_fixtures/testvariables.go
index c086e981..d957d29e 100644
--- a/_fixtures/testvariables.go
+++ b/_fixtures/testvariables.go
@@ -1,7 +1,9 @@
 package main
 
-import "fmt"
-import "runtime"
+import (
+	"fmt"
+	"runtime"
+)
 
 type FooBar struct {
 	Baz int
@@ -56,11 +58,12 @@ func foobar(baz string, bar FooBar) {
 		f    = barfoo
 		ms   = Nest{0, &Nest{1, &Nest{2, &Nest{3, &Nest{4, nil}}}}} // Test recursion capping
 		ba   = make([]int, 200, 200)                                // Test array size capping
+		mp   = map[int]interface{}{1: 42, 2: 43}
 	)
 
 	runtime.Breakpoint()
 	barfoo()
-	fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ba, p1)
+	fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ba, p1, mp)
 }
 
 var p1 = 10
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index e4adcb8b..43ae875b 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -644,6 +644,7 @@ func TestIssue387(t *testing.T) {
 }
 
 func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) {
+	t.Helper()
 	outstr := term.MustExec(listcmd)
 	lines := strings.Split(outstr, "\n")
 
@@ -688,10 +689,10 @@ func TestListCmd(t *testing.T) {
 	withTestTerminal("testvariables", t, func(term *FakeTerminal) {
 		term.MustExec("continue")
 		term.MustExec("continue")
-		listIsAt(t, term, "list", 25, 20, 30)
-		listIsAt(t, term, "list 69", 69, 64, 70)
-		listIsAt(t, term, "frame 1 list", 62, 57, 67)
-		listIsAt(t, term, "frame 1 list 69", 69, 64, 70)
+		listIsAt(t, term, "list", 27, 22, 32)
+		listIsAt(t, term, "list 69", 69, 64, 73)
+		listIsAt(t, term, "frame 1 list", 65, 60, 70)
+		listIsAt(t, term, "frame 1 list 69", 69, 64, 73)
 		_, err := term.Exec("frame 50 list")
 		if err == nil {
 			t.Fatalf("Expected error requesting 50th frame")
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index b35f888f..0f3f8a55 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -61,7 +61,20 @@ func (c *Client) expectReadProtocolMessage(t *testing.T) dap.Message {
 
 func (c *Client) ExpectErrorResponse(t *testing.T) *dap.ErrorResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ErrorResponse)
+	er := c.expectReadProtocolMessage(t).(*dap.ErrorResponse)
+	if er.Body.Error.ShowUser {
+		t.Errorf("\ngot %#v\nwant ShowUser=false", er)
+	}
+	return er
+}
+
+func (c *Client) ExpectVisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
+	t.Helper()
+	er := c.expectReadProtocolMessage(t).(*dap.ErrorResponse)
+	if !er.Body.Error.ShowUser {
+		t.Errorf("\ngot %#v\nwant ShowUser=true", er)
+	}
+	return er
 }
 
 func (c *Client) expectErrorResponse(t *testing.T, id int, message string) *dap.ErrorResponse {
@@ -177,6 +190,11 @@ func (c *Client) ExpectVariablesResponse(t *testing.T) *dap.VariablesResponse {
 	return c.expectReadProtocolMessage(t).(*dap.VariablesResponse)
 }
 
+func (c *Client) ExpectEvaluateResponse(t *testing.T) *dap.EvaluateResponse {
+	t.Helper()
+	return c.expectReadProtocolMessage(t).(*dap.EvaluateResponse)
+}
+
 func (c *Client) ExpectTerminateResponse(t *testing.T) *dap.TerminateResponse {
 	t.Helper()
 	return c.expectReadProtocolMessage(t).(*dap.TerminateResponse)
@@ -485,8 +503,12 @@ func (c *Client) TerminateThreadsRequest() {
 }
 
 // EvaluateRequest sends a 'evaluate' request.
-func (c *Client) EvaluateRequest() {
-	c.send(&dap.EvaluateRequest{Request: *c.newRequest("evaluate")})
+func (c *Client) EvaluateRequest(expr string, fid int, context string) {
+	request := &dap.EvaluateRequest{Request: *c.newRequest("evaluate")}
+	request.Arguments.Expression = expr
+	request.Arguments.FrameId = fid
+	request.Arguments.Context = context
+	c.send(request)
 }
 
 // StepInTargetsRequest sends a 'stepInTargets' request.
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index f89fff45..aa939d45 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -98,14 +98,18 @@ func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.F
 //                                 :  7 >> threads
 //                                 :  7 << threads (Dummy)
 //                                 :  8 >> stackTrace
-//                                 :  8 << stackTrace (Unable to produce stack trace)
+//                                 :  8 << error (Unable to produce stack trace)
 //                                 :  9 >> stackTrace
-//                                 :  9 << stackTrace (Unable to produce stack trace)
-// - User selects "Continue"       : 10 >> continue
-//                                 : 10 << continue
+//                                 :  9 << error (Unable to produce stack trace)
+// - User evaluates bad expression : 10 >> evaluate
+//                                 : 10 << error (unable to find function context)
+// - User evaluates good expression: 11 >> evaluate
+//                                 : 11 << evaluate
+// - User selects "Continue"       : 12 >> continue
+//                                 : 12 << continue
 // - Program runs to completion    :    << terminated event
-//                                 : 11 >> disconnect
-//                                 : 11 << disconnect
+//                                 : 13 >> disconnect
+//                                 : 13 << disconnect
 // This test exhaustively tests Seq and RequestSeq on all messages from the
 // server. Other tests do not necessarily need to repeat all these checks.
 func TestStopOnEntry(t *testing.T) {
@@ -173,36 +177,50 @@ func TestStopOnEntry(t *testing.T) {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7 len(Threads)=1", tResp)
 		}
 
-		// 8 >> stackTrace, << stackTrace
+		// 8 >> stackTrace, << error
 		client.StackTraceRequest(1, 0, 20)
 		stResp := client.ExpectErrorResponse(t)
 		if stResp.Seq != 0 || stResp.RequestSeq != 8 || stResp.Body.Error.Format != "Unable to produce stack trace: unknown goroutine 1" {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=8 Format=\"Unable to produce stack trace: unknown goroutine 1\"", stResp)
 		}
 
-		// 9 >> stackTrace, << stackTrace
+		// 9 >> stackTrace, << error
 		client.StackTraceRequest(1, 0, 20)
 		stResp = client.ExpectErrorResponse(t)
 		if stResp.Seq != 0 || stResp.RequestSeq != 9 || stResp.Body.Error.Id != 2004 {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=9 Id=2004", stResp)
 		}
 
-		// 10 >> continue, << continue, << terminated
+		// 10 >> evaluate, << error
+		client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
+		erResp := client.ExpectVisibleErrorResponse(t)
+		if erResp.Seq != 0 || erResp.RequestSeq != 10 || erResp.Body.Error.Id != 2009 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=2009", erResp)
+		}
+
+		// 11 >> evaluate, << evaluate
+		client.EvaluateRequest("1+1", 0 /*no frame specified*/, "repl")
+		evResp := client.ExpectEvaluateResponse(t)
+		if evResp.Seq != 0 || evResp.RequestSeq != 11 || evResp.Body.Result != "2" {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Result=2", evResp)
+		}
+
+		// 12 >> continue, << continue, << terminated
 		client.ContinueRequest(1)
 		contResp := client.ExpectContinueResponse(t)
-		if contResp.Seq != 0 || contResp.RequestSeq != 10 || !contResp.Body.AllThreadsContinued {
-			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Body.AllThreadsContinued=true", contResp)
+		if contResp.Seq != 0 || contResp.RequestSeq != 12 || !contResp.Body.AllThreadsContinued {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12 Body.AllThreadsContinued=true", contResp)
 		}
 		termEvent := client.ExpectTerminatedEvent(t)
 		if termEvent.Seq != 0 {
 			t.Errorf("\ngot %#v\nwant Seq=0", termEvent)
 		}
 
-		// 11 >> disconnect, << disconnect
+		// 13 >> disconnect, << disconnect
 		client.DisconnectRequest()
 		dResp := client.ExpectDisconnectResponse(t)
-		if dResp.Seq != 0 || dResp.RequestSeq != 11 {
-			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=11", dResp)
+		if dResp.Seq != 0 || dResp.RequestSeq != 13 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=13", dResp)
 		}
 	})
 }
@@ -565,12 +583,12 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 			// Breakpoints are set within the program
 			fixture.Source, []int{},
 			[]onBreakpoint{{
-				// Stop at line 62
+				// Stop at first breakpoint
 				execute: func() {
 					client.StackTraceRequest(1, 0, 20)
 					stack := client.ExpectStackTraceResponse(t)
 
-					startLineno := 62
+					startLineno := 65
 					if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 						// Go1.15 on windows inserts a NOP after the call to
 						// runtime.Breakpoint and marks it same line as the
@@ -611,7 +629,7 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 
 					client.VariablesRequest(1001)
 					locals := client.ExpectVariablesResponse(t)
-					expectChildren(t, locals, "Locals", 29)
+					expectChildren(t, locals, "Locals", 30)
 
 					// reflect.Kind == Bool
 					expectVarExact(t, locals, -1, "b1", "true", noChildren)
@@ -775,12 +793,12 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 				},
 				disconnect: false,
 			}, {
-				// Stop at line 25
+				// Stop at second breakpoint
 				execute: func() {
 					// Frame ids get reset at each breakpoint.
 					client.StackTraceRequest(1, 0, 20)
 					stack := client.ExpectStackTraceResponse(t)
-					expectStackFrames(t, stack, 25, 1000, 5, 5)
+					expectStackFrames(t, stack, 27, 1000, 5, 5)
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
@@ -1146,7 +1164,7 @@ func TestSetBreakpoint(t *testing.T) {
 			fixture.Source, []int{16}, // b main.main
 			[]onBreakpoint{{
 				execute: func() {
-					handleStop(t, client, 1, 16)
+					handleStop(t, client, 1, "main.main", 16)
 
 					type Breakpoint struct {
 						line      int
@@ -1180,7 +1198,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ContinueRequest(1)
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
-					handleStop(t, client, 1, 18)
+					handleStop(t, client, 1, "main.main", 18)
 
 					// Set another breakpoint inside the loop in loop(), twice to trigger error
 					client.SetBreakpointsRequest(fixture.Source, []int{8, 8})
@@ -1190,7 +1208,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ContinueRequest(1)
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
-					handleStop(t, client, 1, 8)
+					handleStop(t, client, 1, "main.loop", 8)
 					client.VariablesRequest(1001) // Locals
 					locals := client.ExpectVariablesResponse(t)
 					expectVarExact(t, locals, 0, "i", "0", noChildren) // i == 0
@@ -1203,7 +1221,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ContinueRequest(1)
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
-					handleStop(t, client, 1, 8)
+					handleStop(t, client, 1, "main.loop", 8)
 					client.VariablesRequest(1001) // Locals
 					locals = client.ExpectVariablesResponse(t)
 					expectVarExact(t, locals, 0, "i", "3", noChildren) // i == 3
@@ -1216,7 +1234,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ContinueRequest(1)
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
-					handleStop(t, client, 1, 8)
+					handleStop(t, client, 1, "main.loop", 8)
 					client.VariablesRequest(1001) // Locals
 					locals = client.ExpectVariablesResponse(t)
 					expectVarExact(t, locals, 0, "i", "4", noChildren) // i == 4
@@ -1231,6 +1249,305 @@ func TestSetBreakpoint(t *testing.T) {
 	})
 }
 
+// expectEval is a helper for verifying the values within an EvaluateResponse.
+//     value - the value of the evaluated expression
+//     hasRef - true if the evaluated expression should have children and therefore a non-0 variable reference
+//     ref - reference to retrieve children of this evaluated expression (0 if none)
+func expectEval(t *testing.T, got *dap.EvaluateResponse, value string, hasRef bool) (ref int) {
+	t.Helper()
+	if got.Body.Result != value || (got.Body.VariablesReference > 0) != hasRef {
+		t.Errorf("\ngot  %#v\nwant Result=%q hasRef=%t", got, value, hasRef)
+	}
+	return got.Body.VariablesReference
+}
+
+func TestEvaluateRequest(t *testing.T) {
+	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client,
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			fixture.Source, []int{}, // Breakpoint set in the program
+			[]onBreakpoint{{ // Stop at first breakpoint
+				execute: func() {
+					handleStop(t, client, 1, "main.foobar", 65)
+
+					// Variable lookup
+					client.EvaluateRequest("a2", 1000, "this context will be ignored")
+					got := client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "6", noChildren)
+
+					client.EvaluateRequest("a5", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref := expectEval(t, got, "<[]int> (length: 5, cap: 5)", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						a5 := client.ExpectVariablesResponse(t)
+						expectChildren(t, a5, "a5", 5)
+						expectVarExact(t, a5, 0, "[0]", "1", noChildren)
+						expectVarExact(t, a5, 4, "[4]", "5", noChildren)
+					}
+
+					// All (binary and unary) on basic types except <-, ++ and --
+					client.EvaluateRequest("1+1", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "2", noChildren)
+
+					// Comparison operators on any type
+					client.EvaluateRequest("1<2", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "true", noChildren)
+
+					// Type casts between numeric types
+					client.EvaluateRequest("int(2.3)", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "2", noChildren)
+
+					// Type casts of integer constants into any pointer type and vice versa
+					client.EvaluateRequest("(*int)(2)", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref = expectEval(t, got, "<*int>(0x2)", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						expr := client.ExpectVariablesResponse(t)
+						expectChildren(t, expr, "(*int)(2)", 1)
+						// TODO(polina): should this be printed as (unknown int) instead?
+						expectVarExact(t, expr, 0, "", "<int>", noChildren)
+					}
+					// Type casts between string, []byte and []rune
+					client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					// TODO(polina): this is a bug (in vscode-go too). dlv cli prints
+					// []uint8 len: 6, cap: 6, [65,66,67,226,130,172]
+					expectEval(t, got, "nil <[]uint8>", noChildren)
+
+					// Struct member access (i.e. somevar.memberfield)
+					client.EvaluateRequest("ms.Nest.Level", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "1", noChildren)
+
+					// Slicing and indexing operators on arrays, slices and strings
+					client.EvaluateRequest("a5[4]", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "5", noChildren)
+
+					// Map access
+					client.EvaluateRequest("mp[1]", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref = expectEval(t, got, "<interface {}(int)>", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						expr := client.ExpectVariablesResponse(t)
+						expectChildren(t, expr, "mp[1]", 1)
+						expectVarExact(t, expr, 0, "data", "42", noChildren)
+					}
+
+					// Pointer dereference
+					client.EvaluateRequest("*ms.Nest", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref = expectEval(t, got, "<main.Nest>", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						expr := client.ExpectVariablesResponse(t)
+						expectChildren(t, expr, "*ms.Nest", 2)
+						expectVarExact(t, expr, 0, "Level", "1", noChildren)
+					}
+
+					// Calls to builtin functions: cap, len, complex, imag and real
+					client.EvaluateRequest("len(a5)", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "5", noChildren)
+
+					// Type assertion on interface variables (i.e. somevar.(concretetype))
+					client.EvaluateRequest("mp[1].(int)", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "42", noChildren)
+				},
+				disconnect: false,
+			}, { // Stop at second breakpoint
+				execute: func() {
+					handleStop(t, client, 1, "main.barfoo", 27)
+
+					// Top-most frame
+					client.EvaluateRequest("a1", 1000, "this context will be ignored")
+					got := client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "\"bur\"", noChildren)
+					// No frame defaults to top-most frame
+					client.EvaluateRequest("a1", 0, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "\"bur\"", noChildren)
+					// Next frame
+					client.EvaluateRequest("a1", 1001, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "\"foofoofoofoofoofoo\"", noChildren)
+					// Next frame
+					client.EvaluateRequest("a1", 1002, "any context but watch")
+					erres := client.ExpectVisibleErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: could not find symbol value for a1" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
+					}
+					client.EvaluateRequest("a1", 1002, "watch")
+					erres = client.ExpectErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: could not find symbol value for a1" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
+					}
+				},
+				disconnect: false,
+			}})
+	})
+}
+
+func TestEvaluateCallRequest(t *testing.T) {
+	protest.MustSupportFunctionCalls(t, testBackend)
+	runTest(t, "fncall", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client,
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			fixture.Source, []int{88},
+			[]onBreakpoint{{ // Stop in makeclos()
+				execute: func() {
+					handleStop(t, client, 1, "main.makeclos", 88)
+
+					// Topmost frame: both types of expressions should work
+					client.EvaluateRequest("callstacktrace", 1000, "this context will be ignored")
+					client.ExpectEvaluateResponse(t)
+					client.EvaluateRequest("call callstacktrace()", 1000, "this context will be ignored")
+					client.ExpectEvaluateResponse(t)
+
+					// Next frame: only non-call expressions will work
+					client.EvaluateRequest("callstacktrace", 1001, "this context will be ignored")
+					client.ExpectEvaluateResponse(t)
+					client.EvaluateRequest("call callstacktrace()", 1001, "not watch")
+					erres := client.ExpectVisibleErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: call is only supported with topmost stack frame" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call is only supported with topmost stack frame\"", erres)
+					}
+
+					// A call can stop on a breakpoint
+					client.EvaluateRequest("call callbreak()", 1000, "not watch")
+					s := client.ExpectStoppedEvent(t)
+					if s.Body.Reason != "hardcoded breakpoint" {
+						t.Errorf("\ngot %#v\nwant Reason=\"hardcoded breakpoint\"", s)
+					}
+					erres = client.ExpectVisibleErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: call stopped" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call stopped\"", erres)
+					}
+
+					// A call during a call causes an error
+					client.EvaluateRequest("call callstacktrace()", 1000, "not watch")
+					erres = client.ExpectVisibleErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: cannot call function while another function call is already in progress" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: cannot call function while another function call is already in progress\"", erres)
+					}
+
+					// Complete the call and get back to original breakpoint in makeclos()
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.ExpectStoppedEvent(t)
+					handleStop(t, client, 1, "main.makeclos", 88)
+
+					// Inject a call for the same function that is stopped at breakpoint:
+					// it might stop at the exact same breakpoint on the same goroutine,
+					// but we should still detect that its an injected call that stopped
+					// and not the return to the original point of injection after it
+					// completed.
+					client.EvaluateRequest("call makeclos(nil)", 1000, "not watch")
+					client.ExpectStoppedEvent(t)
+					erres = client.ExpectVisibleErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: call stopped" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call stopped\"", erres)
+					}
+					if (goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) && (runtime.GOOS == "linux" || runtime.GOOS == "windows")) ||
+						runtime.GOOS == "freebsd" {
+						handleStop(t, client, 1, "runtime.debugCallWrap", -1)
+					} else {
+						handleStop(t, client, 1, "main.makeclos", 88)
+					}
+
+					// Complete the call and get back to original breakpoint in makeclos()
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.ExpectStoppedEvent(t)
+					handleStop(t, client, 1, "main.makeclos", 88)
+				},
+				disconnect: false,
+			}, { // Stop at runtime breakpoint
+				execute: func() {
+					handleStop(t, client, 1, "main.main", 197)
+
+					// No return values
+					client.EvaluateRequest("call call0(1, 2)", 1000, "this context will be ignored")
+					got := client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "", noChildren)
+					// One unnamed return value
+					client.EvaluateRequest("call call1(one, two)", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref := expectEval(t, got, "3", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						rv := client.ExpectVariablesResponse(t)
+						expectChildren(t, rv, "rv", 1)
+						expectVarExact(t, rv, 0, "~r2", "3", noChildren)
+					}
+					// One named return value
+					// Panic doesn't panic, but instead returns the error as a named return variable
+					client.EvaluateRequest("call callpanic()", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref = expectEval(t, got, "<interface {}(string)>", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						rv := client.ExpectVariablesResponse(t)
+						expectChildren(t, rv, "rv", 1)
+						ref = expectVarExact(t, rv, 0, "~panic", "<interface {}(string)>", hasChildren)
+						if ref > 0 {
+							client.VariablesRequest(ref)
+							p := client.ExpectVariablesResponse(t)
+							expectChildren(t, p, "~panic", 1)
+							expectVarExact(t, p, 0, "data", "\"callpanic panicked\"", noChildren)
+						}
+					}
+					// Multiple return values
+					client.EvaluateRequest("call call2(one, two)", 1000, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					ref = expectEval(t, got, "1, 2", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						rvs := client.ExpectVariablesResponse(t)
+						expectChildren(t, rvs, "rvs", 2)
+						expectVarExact(t, rvs, 0, "~r2", "1", noChildren)
+						expectVarExact(t, rvs, 1, "~r3", "2", noChildren)
+					}
+					// No frame defaults to top-most frame
+					client.EvaluateRequest("call call1(one, two)", 0, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "3", hasChildren)
+					// Extra spaces don't matter
+					client.EvaluateRequest(" call  call1(one, one) ", 0, "this context will be ignored")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "2", hasChildren)
+					// Just 'call', even with extra space, is treated as {expression}
+					client.EvaluateRequest("call ", 1000, "watch")
+					got = client.ExpectEvaluateResponse(t)
+					expectEval(t, got, "\"this is a variable named `call`\"", noChildren)
+					// Call error
+					client.EvaluateRequest("call call1(one)", 1000, "watch")
+					erres := client.ExpectErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: not enough arguments" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: not enough arguments\"", erres)
+					}
+					// Call can exit
+					client.EvaluateRequest("call callexit()", 1000, "this context will be ignored")
+					client.ExpectTerminatedEvent(t)
+				},
+				disconnect: true,
+			}})
+	})
+}
+
 func TestNextAndStep(t *testing.T) {
 	runTest(t, "testinline", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSessionWithBPs(t, client,
@@ -1242,32 +1559,32 @@ func TestNextAndStep(t *testing.T) {
 			fixture.Source, []int{11},
 			[]onBreakpoint{{ // Stop at line 11
 				execute: func() {
-					handleStop(t, client, 1, 11)
+					handleStop(t, client, 1, "main.initialize", 11)
 
-					expectStop := func(line int) {
+					expectStop := func(fun string, line int) {
 						t.Helper()
 						se := client.ExpectStoppedEvent(t)
 						if se.Body.Reason != "step" || se.Body.ThreadId != 1 || !se.Body.AllThreadsStopped {
 							t.Errorf("got %#v, want Reason=\"step\", ThreadId=1, AllThreadsStopped=true", se)
 						}
-						handleStop(t, client, 1, line)
+						handleStop(t, client, 1, fun, line)
 					}
 
 					client.StepOutRequest(1)
 					client.ExpectStepOutResponse(t)
-					expectStop(18)
+					expectStop("main.main", 18)
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					expectStop(19)
+					expectStop("main.main", 19)
 
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
-					expectStop(5)
+					expectStop("main.inlineThis", 5)
 
 					client.NextRequest(-10000 /*this is ignored*/)
 					client.ExpectNextResponse(t)
-					expectStop(6)
+					expectStop("main.inlineThis", 6)
 				},
 				disconnect: false,
 			}})
@@ -1288,7 +1605,7 @@ func TestBadAccess(t *testing.T) {
 			fixture.Source, []int{4},
 			[]onBreakpoint{{ // Stop at line 4
 				execute: func() {
-					handleStop(t, client, 1, 4)
+					handleStop(t, client, 1, "main.main", 4)
 
 					expectStoppedOnError := func(errorPrefix string) {
 						t.Helper()
@@ -1338,7 +1655,7 @@ func TestPanicBreakpointOnContinue(t *testing.T) {
 			fixture.Source, []int{5},
 			[]onBreakpoint{{
 				execute: func() {
-					handleStop(t, client, 1, 5)
+					handleStop(t, client, 1, "main.main", 5)
 
 					client.ContinueRequest(1)
 					client.ExpectContinueResponse(t)
@@ -1370,7 +1687,7 @@ func TestPanicBreakpointOnNext(t *testing.T) {
 			fixture.Source, []int{5},
 			[]onBreakpoint{{
 				execute: func() {
-					handleStop(t, client, 1, 5)
+					handleStop(t, client, 1, "main.main", 5)
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
@@ -1397,7 +1714,7 @@ func TestFatalThrowBreakpoint(t *testing.T) {
 			fixture.Source, []int{3},
 			[]onBreakpoint{{
 				execute: func() {
-					handleStop(t, client, 1, 3)
+					handleStop(t, client, 1, "main.main", 3)
 
 					client.ContinueRequest(1)
 					client.ExpectContinueResponse(t)
@@ -1416,15 +1733,23 @@ func TestFatalThrowBreakpoint(t *testing.T) {
 // a client at a breakpoint or another non-terminal stop event.
 // The details have been tested by other tests,
 // so this is just a sanity check.
-func handleStop(t *testing.T, client *daptest.Client, thread, line int) {
+// Skips line check if line is -1.
+func handleStop(t *testing.T, client *daptest.Client, thread int, name string, line int) {
 	t.Helper()
 	client.ThreadsRequest()
 	client.ExpectThreadsResponse(t)
 
 	client.StackTraceRequest(thread, 0, 20)
 	st := client.ExpectStackTraceResponse(t)
-	if len(st.Body.StackFrames) < 1 || st.Body.StackFrames[0].Line != line {
-		t.Errorf("\ngot  %#v\nwant Line=%d", st, line)
+	if len(st.Body.StackFrames) < 1 {
+		t.Errorf("\ngot  %#v\nwant len(stackframes) => 1", st)
+	} else {
+		if line != -1 && st.Body.StackFrames[0].Line != line {
+			t.Errorf("\ngot  %#v\nwant Line=%d", st, line)
+		}
+		if st.Body.StackFrames[0].Name != name {
+			t.Errorf("\ngot  %#v\nwant Name=%q", st, name)
+		}
 	}
 
 	client.ScopesRequest(1000)
@@ -1626,9 +1951,6 @@ func TestRequiredNotYetImplementedResponses(t *testing.T) {
 
 		client.PauseRequest()
 		expectNotYetImplemented("pause")
-
-		client.EvaluateRequest()
-		expectNotYetImplemented("evaluate")
 	})
 }
 
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index 1c892754..3860b700 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -442,6 +442,7 @@ func TestLocalVariables(t *testing.T) {
 				{"f32", true, "1.2", "", "float32", nil},
 				{"i32", true, "[2]int32 [1,2]", "", "[2]int32", nil},
 				{"i8", true, "1", "", "int8", nil},
+				{"mp", true, "map[int]interface {} [1: 42, 2: 43, ]", "", "map[int]interface {}", nil},
 				{"ms", true, "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil},
 				{"neg", true, "-1", "", "int", nil},
 				{"u16", true, "65535", "", "uint16", nil},