Repository: go-delve/delve
Pull request #2185 (2020-09-28 09:53:31)

service/dap: support evaluate requests with expressions and calls
updates #1515 

aarzilli: PS. see the failure of TestScopesAndVariables on windows/go1.15. I think you need to relax that test, it keeps going back and forth.
aarzilli: I think travis is running the tests because of the merge conflict. If you don't know how to get the full log of cirrus-ci, it's here: https://cirrus-ci.com/task/6100324710612992.
polinasok: It took me a moment to figure out, but I did eventually get to the log by clicking some obscure link on the bottom. Let's see what things look like after the merge.
polinasok: I think the proc test failures are unrelated.

Patch:
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/arch.go b/pkg/proc/arch.go
index c205e6dd..732b8704 100644
--- a/pkg/proc/arch.go
+++ b/pkg/proc/arch.go
@@ -9,13 +9,14 @@ import (
 type Arch struct {
 	Name string // architecture name
 
-	ptrSize               int
-	maxInstructionLength  int
-	prologues             []opcodeSeq
-	breakpointInstruction []byte
-	breakInstrMovesPC     bool
-	derefTLS              bool
-	usesLR                bool // architecture uses a link register, also called RA on some architectures
+	ptrSize                  int
+	maxInstructionLength     int
+	prologues                []opcodeSeq
+	breakpointInstruction    []byte
+	altBreakpointInstruction []byte
+	breakInstrMovesPC        bool
+	derefTLS                 bool
+	usesLR                   bool // architecture uses a link register, also called RA on some architectures
 
 	// asmDecode decodes the assembly instruction starting at mem[0:] into asmInst.
 	// It assumes that the Loc and AtPC fields of asmInst have already been filled.
@@ -66,6 +67,11 @@ func (a *Arch) BreakpointInstruction() []byte {
 	return a.breakpointInstruction
 }
 
+// AltBreakpointInstruction returns an alternate encoding for the breakpoint instruction.
+func (a *Arch) AltBreakpointInstruction() []byte {
+	return a.altBreakpointInstruction
+}
+
 // BreakInstrMovesPC is true if hitting the breakpoint instruction advances the
 // instruction counter by the size of the breakpoint instruction.
 func (a *Arch) BreakInstrMovesPC() bool {
diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go
index bcd7cb27..443f54b9 100644
--- a/pkg/proc/breakpoints.go
+++ b/pkg/proc/breakpoints.go
@@ -422,11 +422,11 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
 
 	g, err := GetG(thread)
 	if err != nil {
-		return returnInfoError("could not get g", err, thread)
+		return returnInfoError("could not get g", err, thread.ProcessMemory())
 	}
 	scope, err := GoroutineScope(thread)
 	if err != nil {
-		return returnInfoError("could not get scope", err, thread)
+		return returnInfoError("could not get scope", err, thread.ProcessMemory())
 	}
 	v, err := scope.evalAST(rbpi.retFrameCond)
 	if err != nil || v.Unreadable != nil || v.Kind != reflect.Bool {
@@ -444,12 +444,12 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
 	oldSP := uint64(rbpi.spOffset + int64(g.stack.hi))
 	err = fakeFunctionEntryScope(scope, rbpi.fn, oldFrameOffset, oldSP)
 	if err != nil {
-		return returnInfoError("could not read function entry", err, thread)
+		return returnInfoError("could not read function entry", err, thread.ProcessMemory())
 	}
 
 	vars, err := scope.Locals()
 	if err != nil {
-		return returnInfoError("could not evaluate return variables", err, thread)
+		return returnInfoError("could not evaluate return variables", err, thread.ProcessMemory())
 	}
 	vars = filterVariables(vars, func(v *Variable) bool {
 		return (v.Flags & VariableReturnArgument) != 0
diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go
index abb05b78..93bb277d 100644
--- a/pkg/proc/core/core.go
+++ b/pkg/proc/core/core.go
@@ -154,9 +154,8 @@ type process struct {
 
 	entryPoint uint64
 
-	bi            *proc.BinaryInfo
-	breakpoints   proc.BreakpointMap
-	currentThread *thread
+	bi          *proc.BinaryInfo
+	breakpoints proc.BreakpointMap
 }
 
 var _ proc.ProcessInternal = &process{}
@@ -188,7 +187,7 @@ var (
 	ErrChangeRegisterCore = errors.New("can not change register values of core process")
 )
 
-type openFn func(string, string) (*process, error)
+type openFn func(string, string) (*process, proc.Thread, error)
 
 var openFns = []openFn{readLinuxCore, readAMD64Minidump}
 
@@ -201,9 +200,10 @@ var ErrUnrecognizedFormat = errors.New("unrecognized core format")
 // for external debug files in the directories passed in.
 func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, error) {
 	var p *process
+	var currentThread proc.Thread
 	var err error
 	for _, openFn := range openFns {
-		p, err = openFn(corePath, exePath)
+		p, currentThread, err = openFn(corePath, exePath)
 		if err != ErrUnrecognizedFormat {
 			break
 		}
@@ -212,7 +212,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
 		return nil, err
 	}
 
-	return proc.NewTarget(p, proc.NewTargetConfig{
+	return proc.NewTarget(p, currentThread, proc.NewTargetConfig{
 		Path:                exePath,
 		DebugInfoDirs:       debugInfoDirs,
 		DisableAsyncPreempt: false,
@@ -239,7 +239,7 @@ func (p *process) WriteBreakpoint(addr uint64) (file string, line int, fn *proc.
 func (p *process) Recorded() (bool, string) { return true, "" }
 
 // Restart will only return an error for core files, as they are not executing.
-func (p *process) Restart(string) error { return ErrContinueCore }
+func (p *process) Restart(string) (proc.Thread, error) { return nil, ErrContinueCore }
 
 // ChangeDirection will only return an error as you cannot continue a core process.
 func (p *process) ChangeDirection(proc.Direction) error { return ErrContinueCore }
@@ -262,8 +262,8 @@ func (p *process) ClearCheckpoint(int) error { return errors.New("checkpoint not
 // ReadMemory will return memory from the core file at the specified location and put the
 // read memory into `data`, returning the length read, and returning an error if
 // the length read is shorter than the length of the `data` buffer.
-func (t *thread) ReadMemory(data []byte, addr uint64) (n int, err error) {
-	n, err = t.p.mem.ReadMemory(data, addr)
+func (p *process) ReadMemory(data []byte, addr uint64) (n int, err error) {
+	n, err = p.mem.ReadMemory(data, addr)
 	if err == nil && n != len(data) {
 		err = ErrShortRead
 	}
@@ -272,10 +272,15 @@ func (t *thread) ReadMemory(data []byte, addr uint64) (n int, err error) {
 
 // WriteMemory will only return an error for core files, you cannot write
 // to the memory of a core process.
-func (t *thread) WriteMemory(addr uint64, data []byte) (int, error) {
+func (p *process) WriteMemory(addr uint64, data []byte) (int, error) {
 	return 0, ErrWriteCore
 }
 
+// ProcessMemory returns the memory of this thread's process.
+func (t *thread) ProcessMemory() proc.MemoryReadWriter {
+	return t.p
+}
+
 // Location returns the location of this thread based on
 // the value of the instruction pointer register.
 func (t *thread) Location() (*proc.Location, error) {
@@ -400,9 +405,9 @@ func (p *process) CheckAndClearManualStopRequest() bool {
 	return false
 }
 
-// CurrentThread returns the current active thread.
-func (p *process) CurrentThread() proc.Thread {
-	return p.currentThread
+// Memory returns the process memory.
+func (p *process) Memory() proc.MemoryReadWriter {
+	return p
 }
 
 // Detach will always return nil and have no
@@ -442,8 +447,3 @@ func (p *process) FindThread(threadID int) (proc.Thread, bool) {
 	t, ok := p.Threads[threadID]
 	return t, ok
 }
-
-// SetCurrentThread is used internally by proc.Target to change the current thread.
-func (p *process) SetCurrentThread(th proc.Thread) {
-	p.currentThread = th.(*thread)
-}
diff --git a/pkg/proc/core/linux_core.go b/pkg/proc/core/linux_core.go
index 77c5e108..6861451a 100644
--- a/pkg/proc/core/linux_core.go
+++ b/pkg/proc/core/linux_core.go
@@ -42,7 +42,8 @@ const (
 
 const elfErrorBadMagicNumber = "bad magic number"
 
-func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
+func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) proc.Thread {
+	var currentThread proc.Thread
 	var lastThreadAMD *linuxAMD64Thread
 	var lastThreadARM *linuxARM64Thread
 	for _, note := range notes {
@@ -52,15 +53,15 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
 				t := note.Desc.(*linuxPrStatusAMD64)
 				lastThreadAMD = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
 				p.Threads[int(t.Pid)] = &thread{lastThreadAMD, p, proc.CommonThread{}}
-				if p.currentThread == nil {
-					p.currentThread = p.Threads[int(t.Pid)]
+				if currentThread == nil {
+					currentThread = p.Threads[int(t.Pid)]
 				}
 			} else if machineType == _EM_AARCH64 {
 				t := note.Desc.(*linuxPrStatusARM64)
 				lastThreadARM = &linuxARM64Thread{linutil.ARM64Registers{Regs: &t.Reg}, t}
 				p.Threads[int(t.Pid)] = &thread{lastThreadARM, p, proc.CommonThread{}}
-				if p.currentThread == nil {
-					p.currentThread = p.Threads[int(t.Pid)]
+				if currentThread == nil {
+					currentThread = p.Threads[int(t.Pid)]
 				}
 			}
 		case _NT_FPREGSET:
@@ -79,6 +80,7 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
 			p.pid = int(note.Desc.(*linuxPrPsInfo).Pid)
 		}
 	}
+	return currentThread
 }
 
 // readLinuxCore reads a core file from corePath corresponding to the executable at
@@ -87,35 +89,35 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
 // http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
 // elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
 // and, if absolutely desperate, readelf.c from the binutils source.
-func readLinuxCore(corePath, exePath string) (*process, error) {
+func readLinuxCore(corePath, exePath string) (*process, proc.Thread, error) {
 	coreFile, err := elf.Open(corePath)
 	if err != nil {
 		if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
 			// Go >=1.11 and <1.11 produce different errors when reading a non-elf file.
-			return nil, ErrUnrecognizedFormat
+			return nil, nil, ErrUnrecognizedFormat
 		}
-		return nil, err
+		return nil, nil, err
 	}
 	exe, err := os.Open(exePath)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	exeELF, err := elf.NewFile(exe)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	if coreFile.Type != elf.ET_CORE {
-		return nil, fmt.Errorf("%v is not a core file", coreFile)
+		return nil, nil, fmt.Errorf("%v is not a core file", coreFile)
 	}
 	if exeELF.Type != elf.ET_EXEC && exeELF.Type != elf.ET_DYN {
-		return nil, fmt.Errorf("%v is not an exe file", exeELF)
+		return nil, nil, fmt.Errorf("%v is not an exe file", exeELF)
 	}
 
 	machineType := exeELF.Machine
 	notes, err := readNotes(coreFile, machineType)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	memory := buildMemory(coreFile, exeELF, exe, notes)
 
@@ -127,7 +129,7 @@ func readLinuxCore(corePath, exePath string) (*process, error) {
 	case _EM_AARCH64:
 		bi = proc.NewBinaryInfo("linux", "arm64")
 	default:
-		return nil, fmt.Errorf("unsupported machine type")
+		return nil, nil, fmt.Errorf("unsupported machine type")
 	}
 
 	entryPoint := findEntryPoint(notes, bi.Arch.PtrSize())
@@ -140,8 +142,8 @@ func readLinuxCore(corePath, exePath string) (*process, error) {
 		breakpoints: proc.NewBreakpointMap(),
 	}
 
-	linuxThreadsFromNotes(p, notes, machineType)
-	return p, nil
+	currentThread := linuxThreadsFromNotes(p, notes, machineType)
+	return p, currentThread, nil
 }
 
 type linuxAMD64Thread struct {
diff --git a/pkg/proc/core/windows_amd64_minidump.go b/pkg/proc/core/windows_amd64_minidump.go
index 554a66f4..aeadd11a 100644
--- a/pkg/proc/core/windows_amd64_minidump.go
+++ b/pkg/proc/core/windows_amd64_minidump.go
@@ -7,7 +7,7 @@ import (
 	"github.com/go-delve/delve/pkg/proc/winutil"
 )
 
-func readAMD64Minidump(minidumpPath, exePath string) (*process, error) {
+func readAMD64Minidump(minidumpPath, exePath string) (*process, proc.Thread, error) {
 	var logfn func(string, ...interface{})
 	if logflags.Minidump() {
 		logfn = logflags.MinidumpLogger().Infof
@@ -16,9 +16,9 @@ func readAMD64Minidump(minidumpPath, exePath string) (*process, error) {
 	mdmp, err := minidump.Open(minidumpPath, logfn)
 	if err != nil {
 		if _, isNotAMinidump := err.(minidump.ErrNotAMinidump); isNotAMinidump {
-			return nil, ErrUnrecognizedFormat
+			return nil, nil, ErrUnrecognizedFormat
 		}
-		return nil, err
+		return nil, nil, err
 	}
 
 	memory := &splicedMemory{}
@@ -45,11 +45,12 @@ func readAMD64Minidump(minidumpPath, exePath string) (*process, error) {
 	for i := range mdmp.Threads {
 		th := &mdmp.Threads[i]
 		p.Threads[int(th.ID)] = &thread{&windowsAMD64Thread{th}, p, proc.CommonThread{}}
-		if p.currentThread == nil {
-			p.currentThread = p.Threads[int(th.ID)]
-		}
 	}
-	return p, nil
+	var currentThread proc.Thread
+	if len(mdmp.Threads) > 0 {
+		currentThread = p.Threads[int(mdmp.Threads[0].ID)]
+	}
+	return p, currentThread, nil
 }
 
 type windowsAMD64Thread struct {
diff --git a/pkg/proc/disasm.go b/pkg/proc/disasm.go
index 808a11e9..8d5d50b9 100644
--- a/pkg/proc/disasm.go
+++ b/pkg/proc/disasm.go
@@ -71,7 +71,7 @@ type opcodeSeq []uint64
 // If sameline is set firstPCAfterPrologueDisassembly will always return an
 // address associated with the same line as fn.Entry
 func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (uint64, error) {
-	var mem MemoryReadWriter = p.CurrentThread()
+	mem := p.Memory()
 	breakpoints := p.Breakpoints()
 	bi := p.BinInfo()
 	text, err := disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End, false)
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index 6563e275..07c1608d 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -74,13 +74,6 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error
 		return ThreadScope(ct)
 	}
 
-	var thread MemoryReadWriter
-	if g.Thread == nil {
-		thread = ct
-	} else {
-		thread = g.Thread
-	}
-
 	var opts StacktraceOptions
 	if deferCall > 0 {
 		opts = StacktraceReadDefers
@@ -108,7 +101,7 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error
 		return d.EvalScope(ct)
 	}
 
-	return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
+	return FrameToScope(dbp.BinInfo(), dbp.Memory(), g, locs[frame:]...), nil
 }
 
 // FrameToScope returns a new EvalScope for frames[0].
@@ -145,7 +138,7 @@ func ThreadScope(thread Thread) (*EvalScope, error) {
 	if len(locations) < 1 {
 		return nil, errors.New("could not decode first frame")
 	}
-	return FrameToScope(thread.BinInfo(), thread, nil, locations...), nil
+	return FrameToScope(thread.BinInfo(), thread.ProcessMemory(), nil, locations...), nil
 }
 
 // GoroutineScope returns an EvalScope for the goroutine running on the given thread.
@@ -161,7 +154,7 @@ func GoroutineScope(thread Thread) (*EvalScope, error) {
 	if err != nil {
 		return nil, err
 	}
-	return FrameToScope(thread.BinInfo(), thread, g, locations...), nil
+	return FrameToScope(thread.BinInfo(), thread.ProcessMemory(), g, locations...), nil
 }
 
 // EvalExpression returns the value of the given expression.
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index da7157c2..7ce7ee29 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -309,7 +309,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
 		return nil, err
 	}
 	// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
-	if err := writePointer(bi, thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
+	if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
 		return nil, err
 	}
 
@@ -422,7 +422,7 @@ func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) erro
 	if err := thread.SetSP(sp); err != nil {
 		return err
 	}
-	if err := writePointer(bi, thread, sp, regs.PC()); err != nil {
+	if err := writePointer(bi, thread.ProcessMemory(), sp, regs.PC()); err != nil {
 		return err
 	}
 	return thread.SetPC(callAddr)
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index b1937b86..843e0a30 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -576,7 +576,7 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
 			return nil, err
 		}
 	}
-	tgt, err := proc.NewTarget(p, proc.NewTargetConfig{
+	tgt, err := proc.NewTarget(p, p.currentThread, proc.NewTargetConfig{
 		Path:                path,
 		DebugInfoDirs:       debugInfoDirs,
 		DisableAsyncPreempt: runtime.GOOS == "darwin",
@@ -649,15 +649,9 @@ func (p *gdbProcess) ThreadList() []proc.Thread {
 	return r
 }
 
-// CurrentThread returns the current active
-// selected thread.
-func (p *gdbProcess) CurrentThread() proc.Thread {
-	return p.currentThread
-}
-
-// SetCurrentThread is used internally by proc.Target to change the current thread.
-func (p *gdbProcess) SetCurrentThread(th proc.Thread) {
-	p.currentThread = th.(*gdbThread)
+// Memory returns the process memory.
+func (p *gdbProcess) Memory() proc.MemoryReadWriter {
+	return p
 }
 
 const (
@@ -775,6 +769,7 @@ continueLoop:
 		// the signals that are reported here can not be propagated back to the target process.
 		trapthread.sig = 0
 	}
+	p.currentThread = trapthread
 	return trapthread, stopReason, err
 }
 
@@ -937,9 +932,9 @@ func (p *gdbProcess) Detach(kill bool) error {
 }
 
 // Restart will restart the process from the given position.
-func (p *gdbProcess) Restart(pos string) error {
+func (p *gdbProcess) Restart(pos string) (proc.Thread, error) {
 	if p.tracedir == "" {
-		return proc.ErrNotRecorded
+		return nil, proc.ErrNotRecorded
 	}
 
 	p.exited = false
@@ -952,19 +947,19 @@ func (p *gdbProcess) Restart(pos string) error {
 
 	err := p.conn.restart(pos)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// 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(nil, nil)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	err = p.updateThreadList(&threadUpdater{p: p})
 	if err != nil {
-		return err
+		return nil, err
 	}
 	p.clearThreadSignals()
 	p.clearThreadRegisters()
@@ -973,7 +968,7 @@ func (p *gdbProcess) Restart(pos string) error {
 		p.conn.setBreakpoint(addr)
 	}
 
-	return p.setCurrentBreakpoints()
+	return p.currentThread, p.setCurrentBreakpoints()
 }
 
 // When executes the 'when' command for the Mozilla RR backend.
@@ -1265,8 +1260,8 @@ func (p *gdbProcess) setCurrentBreakpoints() error {
 }
 
 // ReadMemory will read into 'data' memory at the address provided.
-func (t *gdbThread) ReadMemory(data []byte, addr uint64) (n int, err error) {
-	err = t.p.conn.readMemory(data, addr)
+func (p *gdbProcess) ReadMemory(data []byte, addr uint64) (n int, err error) {
+	err = p.conn.readMemory(data, addr)
 	if err != nil {
 		return 0, err
 	}
@@ -1274,8 +1269,12 @@ func (t *gdbThread) ReadMemory(data []byte, addr uint64) (n int, err error) {
 }
 
 // WriteMemory will write into the memory at 'addr' the data provided.
-func (t *gdbThread) WriteMemory(addr uint64, data []byte) (written int, err error) {
-	return t.p.conn.writeMemory(addr, data)
+func (p *gdbProcess) WriteMemory(addr uint64, data []byte) (written int, err error) {
+	return p.conn.writeMemory(addr, data)
+}
+
+func (t *gdbThread) ProcessMemory() proc.MemoryReadWriter {
+	return t.p
 }
 
 // Location returns the current location of this thread.
@@ -1522,18 +1521,18 @@ func (t *gdbThread) reloadGAtPC() error {
 	}
 
 	savedcode := make([]byte, len(movinstr))
-	_, err := t.ReadMemory(savedcode, pc)
+	_, err := t.p.ReadMemory(savedcode, pc)
 	if err != nil {
 		return err
 	}
 
-	_, err = t.WriteMemory(pc, movinstr)
+	_, err = t.p.WriteMemory(pc, movinstr)
 	if err != nil {
 		return err
 	}
 
 	defer func() {
-		_, err0 := t.WriteMemory(pc, savedcode)
+		_, err0 := t.p.WriteMemory(pc, savedcode)
 		if err == nil {
 			err = err0
 		}
diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go
index 62960f42..f22787d1 100644
--- a/pkg/proc/i386_arch.go
+++ b/pkg/proc/i386_arch.go
@@ -24,6 +24,7 @@ func I386Arch(goos string) *Arch {
 		ptrSize:                          4,
 		maxInstructionLength:             15,
 		breakpointInstruction:            i386BreakInstruction,
+		altBreakpointInstruction:         []byte{0xcd, 0x03},
 		breakInstrMovesPC:                true,
 		derefTLS:                         false,
 		prologues:                        prologuesI386,
diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go
index cd1ad0c8..f785480f 100644
--- a/pkg/proc/interface.go
+++ b/pkg/proc/interface.go
@@ -13,6 +13,9 @@ type Process interface {
 	RecordingManipulation
 
 	Breakpoints() *BreakpointMap
+
+	// Memory returns a memory read/writer for this process's memory.
+	Memory() MemoryReadWriter
 }
 
 // ProcessInternal holds a set of methods that are not meant to be called by
@@ -21,12 +24,12 @@ type Process interface {
 // the `proc` package.
 // This is temporary and in support of an ongoing refactor.
 type ProcessInternal interface {
-	SetCurrentThread(Thread)
 	// Restart restarts the recording from the specified position, or from the
 	// last checkpoint if pos == "".
 	// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
 	// number.
-	Restart(pos string) error
+	// Returns the new current thread after the restart has completed.
+	Restart(pos string) (Thread, error)
 	Detach(bool) error
 	ContinueOnce() (trapthread Thread, stopReason StopReason, err error)
 
@@ -91,7 +94,6 @@ type Info interface {
 type ThreadInfo interface {
 	FindThread(threadID int) (Thread, bool)
 	ThreadList() []Thread
-	CurrentThread() Thread
 }
 
 // ProcessManipulation is an interface for changing the execution state of a process.
diff --git a/pkg/proc/linutil/dynamic.go b/pkg/proc/linutil/dynamic.go
index 46eab4db..2e02a774 100644
--- a/pkg/proc/linutil/dynamic.go
+++ b/pkg/proc/linutil/dynamic.go
@@ -44,7 +44,7 @@ func readUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64,
 // dynamicSearchDebug searches for the DT_DEBUG entry in the .dynamic section
 func dynamicSearchDebug(p proc.Process) (uint64, error) {
 	bi := p.BinInfo()
-	mem := p.CurrentThread()
+	mem := p.Memory()
 
 	dynbuf := make([]byte, bi.ElfDynamicSection.Size)
 	_, err := mem.ReadMemory(dynbuf, bi.ElfDynamicSection.Addr)
@@ -73,7 +73,7 @@ func dynamicSearchDebug(p proc.Process) (uint64, error) {
 
 func readPtr(p proc.Process, addr uint64) (uint64, error) {
 	ptrbuf := make([]byte, p.BinInfo().Arch.PtrSize())
-	_, err := p.CurrentThread().ReadMemory(ptrbuf, addr)
+	_, err := p.Memory().ReadMemory(ptrbuf, addr)
 	if err != nil {
 		return 0, err
 	}
@@ -115,7 +115,7 @@ func readCString(p proc.Process, addr uint64) (string, error) {
 	if addr == 0 {
 		return "", nil
 	}
-	mem := p.CurrentThread()
+	mem := p.Memory()
 	buf := make([]byte, 1)
 	r := []byte{}
 	for {
diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go
index 2c00425b..9531239c 100644
--- a/pkg/proc/native/nonative_darwin.go
+++ b/pkg/proc/native/nonative_darwin.go
@@ -59,7 +59,7 @@ func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
 	panic(ErrNativeBackendDisabled)
 }
 
-func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
+func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	panic(ErrNativeBackendDisabled)
 }
 
@@ -81,11 +81,6 @@ func (dbp *nativeProcess) EntryPoint() (uint64, error) {
 	panic(ErrNativeBackendDisabled)
 }
 
-// Blocked returns true if the thread is blocked
-func (t *nativeThread) Blocked() bool {
-	panic(ErrNativeBackendDisabled)
-}
-
 // SetPC sets the value of the PC register.
 func (t *nativeThread) SetPC(pc uint64) error {
 	panic(ErrNativeBackendDisabled)
diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go
index a2a8939d..6509bf54 100644
--- a/pkg/proc/native/proc.go
+++ b/pkg/proc/native/proc.go
@@ -22,8 +22,8 @@ type nativeProcess struct {
 	// List of threads mapped as such: pid -> *Thread
 	threads map[int]*nativeThread
 
-	// Active thread
-	currentThread *nativeThread
+	// Thread used to read and write memory
+	memthread *nativeThread
 
 	os                  *osProcessDetails
 	firstStart          bool
@@ -72,7 +72,7 @@ func (dbp *nativeProcess) Recorded() (bool, string) { return false, "" }
 
 // Restart will always return an error in the native proc backend, only for
 // recorded traces.
-func (dbp *nativeProcess) Restart(string) error { return proc.ErrNotRecorded }
+func (dbp *nativeProcess) Restart(string) (proc.Thread, error) { return nil, proc.ErrNotRecorded }
 
 // ChangeDirection will always return an error in the native proc backend, only for
 // recorded traces.
@@ -166,14 +166,9 @@ func (dbp *nativeProcess) FindThread(threadID int) (proc.Thread, bool) {
 	return th, ok
 }
 
-// CurrentThread returns the current selected, active thread.
-func (dbp *nativeProcess) CurrentThread() proc.Thread {
-	return dbp.currentThread
-}
-
-// SetCurrentThread is used internally by proc.Target to change the current thread.
-func (p *nativeProcess) SetCurrentThread(th proc.Thread) {
-	p.currentThread = th.(*nativeThread)
+// Memory returns the process memory.
+func (dbp *nativeProcess) Memory() proc.MemoryReadWriter {
+	return dbp.memthread
 }
 
 // Breakpoints returns a list of breakpoints currently set.
@@ -209,11 +204,11 @@ func (dbp *nativeProcess) WriteBreakpoint(addr uint64) (string, int, *proc.Funct
 	f, l, fn := dbp.bi.PCToLine(uint64(addr))
 
 	originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
-	_, err := dbp.currentThread.ReadMemory(originalData, addr)
+	_, err := dbp.memthread.ReadMemory(originalData, addr)
 	if err != nil {
 		return "", 0, nil, nil, err
 	}
-	if err := dbp.writeSoftwareBreakpoint(dbp.currentThread, addr); err != nil {
+	if err := dbp.writeSoftwareBreakpoint(dbp.memthread, addr); err != nil {
 		return "", 0, nil, nil, err
 	}
 
@@ -221,7 +216,7 @@ func (dbp *nativeProcess) WriteBreakpoint(addr uint64) (string, int, *proc.Funct
 }
 
 func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
-	return dbp.currentThread.ClearBreakpoint(bp)
+	return dbp.memthread.ClearBreakpoint(bp)
 }
 
 // ContinueOnce will continue the target until it stops.
@@ -231,27 +226,34 @@ func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
 		return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
-	if err := dbp.resume(); err != nil {
-		return nil, proc.StopUnknown, err
-	}
+	for {
 
-	for _, th := range dbp.threads {
-		th.CurrentBreakpoint.Clear()
-	}
+		if err := dbp.resume(); err != nil {
+			return nil, proc.StopUnknown, err
+		}
 
-	if dbp.resumeChan != nil {
-		close(dbp.resumeChan)
-		dbp.resumeChan = nil
-	}
+		for _, th := range dbp.threads {
+			th.CurrentBreakpoint.Clear()
+		}
 
-	trapthread, err := dbp.trapWait(-1)
-	if err != nil {
-		return nil, proc.StopUnknown, err
-	}
-	if err := dbp.stop(trapthread); err != nil {
-		return nil, proc.StopUnknown, err
+		if dbp.resumeChan != nil {
+			close(dbp.resumeChan)
+			dbp.resumeChan = nil
+		}
+
+		trapthread, err := dbp.trapWait(-1)
+		if err != nil {
+			return nil, proc.StopUnknown, err
+		}
+		trapthread, err = dbp.stop(trapthread)
+		if err != nil {
+			return nil, proc.StopUnknown, err
+		}
+		if trapthread != nil {
+			dbp.memthread = trapthread
+			return trapthread, proc.StopUnknown, nil
+		}
 	}
-	return trapthread, proc.StopUnknown, err
 }
 
 // FindBreakpoint finds the breakpoint for the given pc.
@@ -282,7 +284,7 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc
 	if !dbp.childProcess {
 		stopReason = proc.StopAttached
 	}
-	return proc.NewTarget(dbp, proc.NewTargetConfig{
+	return proc.NewTarget(dbp, dbp.memthread, proc.NewTargetConfig{
 		Path:                path,
 		DebugInfoDirs:       debugInfoDirs,
 		DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd",
diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go
index 5d5320b6..f44aa312 100644
--- a/pkg/proc/native/proc_darwin.go
+++ b/pkg/proc/native/proc_darwin.go
@@ -113,12 +113,12 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
 	if err != nil {
 		return nil, err
 	}
-	if err := dbp.stop(nil); err != nil {
+	if _, err := dbp.stop(nil); err != nil {
 		return nil, err
 	}
 
 	dbp.os.initialized = true
-	dbp.currentThread = trapthread
+	dbp.memthread = trapthread
 
 	tgt, err := dbp.initialize(argv0Go, []string{})
 	if err != nil {
@@ -188,7 +188,7 @@ func (dbp *nativeProcess) kill() (err error) {
 func (dbp *nativeProcess) requestManualStop() (err error) {
 	var (
 		task          = C.mach_port_t(dbp.os.task)
-		thread        = C.mach_port_t(dbp.currentThread.os.threadAct)
+		thread        = C.mach_port_t(dbp.memthread.os.threadAct)
 		exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
 	)
 	dbp.os.halt = true
@@ -267,8 +267,8 @@ func (dbp *nativeProcess) addThread(port int, attach bool) (*nativeThread, error
 	}
 	dbp.threads[port] = thread
 	thread.os.threadAct = C.thread_act_t(port)
-	if dbp.currentThread == nil {
-		dbp.currentThread = thread
+	if dbp.memthread == nil {
+		dbp.memthread = thread
 	}
 	return thread, nil
 }
@@ -422,35 +422,35 @@ func (dbp *nativeProcess) resume() error {
 }
 
 // stop stops all running threads and sets breakpoints
-func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
+func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 	for _, th := range dbp.threads {
 		if !th.Stopped() {
 			if err := th.stop(); err != nil {
-				return dbp.exitGuard(err)
+				return nil, dbp.exitGuard(err)
 			}
 		}
 	}
 
 	ports, err := dbp.waitForStop()
 	if err != nil {
-		return err
+		return nil, err
 	}
 	if !dbp.os.initialized {
-		return nil
+		return nil, nil
 	}
 	trapthread.SetCurrentBreakpoint(true)
 	for _, port := range ports {
 		if th, ok := dbp.threads[port]; ok {
 			err := th.SetCurrentBreakpoint(true)
 			if err != nil {
-				return err
+				return nil, err
 			}
 		}
 	}
-	return nil
+	return trapthread, nil
 }
 
 func (dbp *nativeProcess) detach(kill bool) error {
diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go
index 9457161e..a2a3ab16 100644
--- a/pkg/proc/native/proc_freebsd.go
+++ b/pkg/proc/native/proc_freebsd.go
@@ -180,8 +180,8 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error)
 		os:  new(osSpecificDetails),
 	}
 
-	if dbp.currentThread == nil {
-		dbp.currentThread = dbp.threads[tid]
+	if dbp.memthread == nil {
+		dbp.memthread = dbp.threads[tid]
 	}
 
 	return dbp.threads[tid], nil
@@ -347,19 +347,19 @@ func (dbp *nativeProcess) resume() error {
 
 // Used by ContinueOnce
 // stop stops all running threads and sets breakpoints
-func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
+func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 	// set breakpoints on all threads
 	for _, th := range dbp.threads {
 		if th.CurrentBreakpoint.Breakpoint == nil {
 			if err := th.SetCurrentBreakpoint(true); err != nil {
-				return err
+				return nil, err
 			}
 		}
 	}
-	return nil
+	return trapthread, nil
 }
 
 // Used by Detach
diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go
index 00a01038..40255276 100644
--- a/pkg/proc/native/proc_linux.go
+++ b/pkg/proc/native/proc_linux.go
@@ -253,8 +253,8 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error)
 		dbp: dbp,
 		os:  new(osSpecificDetails),
 	}
-	if dbp.currentThread == nil {
-		dbp.currentThread = dbp.threads[tid]
+	if dbp.memthread == nil {
+		dbp.memthread = dbp.threads[tid]
 	}
 	return dbp.threads[tid], nil
 }
@@ -486,9 +486,9 @@ func (dbp *nativeProcess) resume() error {
 }
 
 // stop stops all running threads and sets breakpoints
-func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
+func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
 	for _, th := range dbp.threads {
@@ -500,7 +500,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
 	for {
 		th, err := dbp.trapWaitInternal(-1, trapWaitNohang)
 		if err != nil {
-			return dbp.exitGuard(err)
+			return nil, dbp.exitGuard(err)
 		}
 		if th == nil {
 			break
@@ -511,7 +511,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
 	for _, th := range dbp.threads {
 		if th.os.running {
 			if err := th.stop(); err != nil {
-				return dbp.exitGuard(err)
+				return nil, dbp.exitGuard(err)
 			}
 		}
 	}
@@ -530,23 +530,76 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
 		}
 		_, err := dbp.trapWaitInternal(-1, trapWaitHalt)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
 	if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
-		return err
+		return nil, err
 	}
 
+	switchTrapthread := false
+
 	// set breakpoints on SIGTRAP threads
 	for _, th := range dbp.threads {
 		if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp {
 			if err := th.SetCurrentBreakpoint(true); err != nil {
-				return err
+				return nil, err
+			}
+		}
+		if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp && (th.Status != nil) && ((*sys.WaitStatus)(th.Status).StopSignal() == sys.SIGTRAP) && dbp.BinInfo().Arch.BreakInstrMovesPC() {
+			manualStop := false
+			if th.ThreadID() == trapthread.ThreadID() {
+				dbp.stopMu.Lock()
+				manualStop = dbp.manualStopRequested
+				dbp.stopMu.Unlock()
+			}
+			if !manualStop {
+				// Thread received a SIGTRAP but we don't have a breakpoint for it and
+				// it wasn't sent by a manual stop request. It's either a hardcoded
+				// breakpoint or a phantom breakpoint hit (a breakpoint that was hit but
+				// we have removed before we could receive its signal). Check if it is a
+				// hardcoded breakpoint, otherwise rewind the thread.
+				isHardcodedBreakpoint := false
+				pc, _ := th.PC()
+				for _, bpinstr := range [][]byte{
+					dbp.BinInfo().Arch.BreakpointInstruction(),
+					dbp.BinInfo().Arch.AltBreakpointInstruction()} {
+					if bpinstr == nil {
+						continue
+					}
+					buf := make([]byte, len(bpinstr))
+					_, _ = th.ReadMemory(buf, pc-uint64(len(buf)))
+					if bytes.Equal(buf, bpinstr) {
+						isHardcodedBreakpoint = true
+						break
+					}
+				}
+				if !isHardcodedBreakpoint {
+					// phantom breakpoint hit
+					_ = th.SetPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
+					th.os.setbp = false
+					if trapthread.ThreadID() == th.ThreadID() {
+						// Will switch to a different thread for trapthread because we don't
+						// want pkg/proc to believe that this thread was stopped by a
+						// hardcoded breakpoint.
+						switchTrapthread = true
+					}
+				}
 			}
 		}
 	}
-	return nil
+
+	if switchTrapthread {
+		trapthread = nil
+		for _, th := range dbp.threads {
+			if th.os.setbp && th.ThreadID() != trapthread.ThreadID() {
+				return th, nil
+			}
+		}
+	}
+
+	return trapthread, nil
 }
 
 func (dbp *nativeProcess) detach(kill bool) error {
diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go
index c7ce90a5..d9ee5999 100644
--- a/pkg/proc/native/proc_windows.go
+++ b/pkg/proc/native/proc_windows.go
@@ -206,8 +206,8 @@ func (dbp *nativeProcess) addThread(hThread syscall.Handle, threadID int, attach
 	}
 	thread.os.hThread = hThread
 	dbp.threads[threadID] = thread
-	if dbp.currentThread == nil {
-		dbp.currentThread = dbp.threads[threadID]
+	if dbp.memthread == nil {
+		dbp.memthread = dbp.threads[threadID]
 	}
 	if suspendNewThreads {
 		_, err := _SuspendThread(thread.os.hThread)
@@ -407,9 +407,9 @@ func (dbp *nativeProcess) resume() error {
 }
 
 // stop stops all running threads threads and sets breakpoints
-func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
+func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
 	dbp.os.running = false
@@ -424,15 +424,15 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
 	// call to _ContinueDebugEvent will resume execution of some of the
 	// target threads.
 
-	err = trapthread.SetCurrentBreakpoint(true)
+	err := trapthread.SetCurrentBreakpoint(true)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	for _, thread := range dbp.threads {
 		_, err := _SuspendThread(thread.os.hThread)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
@@ -446,18 +446,18 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
 			}
 		})
 		if err != nil {
-			return err
+			return nil, err
 		}
 		if tid == 0 {
 			break
 		}
 		err = dbp.threads[tid].SetCurrentBreakpoint(true)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
-	return nil
+	return trapthread, nil
 }
 
 func (dbp *nativeProcess) detach(kill bool) error {
diff --git a/pkg/proc/native/registers_freebsd_amd64.go b/pkg/proc/native/registers_freebsd_amd64.go
index 7e7de291..73ecc415 100644
--- a/pkg/proc/native/registers_freebsd_amd64.go
+++ b/pkg/proc/native/registers_freebsd_amd64.go
@@ -71,7 +71,7 @@ func registers(thread *nativeThread) (proc.Registers, error) {
 }
 
 const (
-	_X86_XSTATE_MAX_SIZE = 2688
+	_X86_XSTATE_MAX_SIZE = 2696
 	_NT_X86_XSTATE       = 0x202
 
 	_XSAVE_HEADER_START          = 512
diff --git a/pkg/proc/native/registers_linux_amd64.go b/pkg/proc/native/registers_linux_amd64.go
index 5066af01..a16a08a3 100644
--- a/pkg/proc/native/registers_linux_amd64.go
+++ b/pkg/proc/native/registers_linux_amd64.go
@@ -66,7 +66,7 @@ func registers(thread *nativeThread) (proc.Registers, error) {
 }
 
 const (
-	_X86_XSTATE_MAX_SIZE = 2688
+	_X86_XSTATE_MAX_SIZE = 2696
 	_NT_X86_XSTATE       = 0x202
 
 	_XSAVE_HEADER_START          = 512
diff --git a/pkg/proc/native/threads.go b/pkg/proc/native/threads.go
index 65b5ab75..c15d63bd 100644
--- a/pkg/proc/native/threads.go
+++ b/pkg/proc/native/threads.go
@@ -175,3 +175,8 @@ func (t *nativeThread) PC() (uint64, error) {
 	}
 	return regs.PC(), nil
 }
+
+// ProcessMemory returns this thread's process memory.
+func (t *nativeThread) ProcessMemory() proc.MemoryReadWriter {
+	return t.dbp.Memory()
+}
diff --git a/pkg/proc/native/threads_darwin.go b/pkg/proc/native/threads_darwin.go
index 183ddff9..fe12bede 100644
--- a/pkg/proc/native/threads_darwin.go
+++ b/pkg/proc/native/threads_darwin.go
@@ -85,25 +85,6 @@ func (t *nativeThread) resume() error {
 	return nil
 }
 
-func (t *nativeThread) Blocked() bool {
-	// TODO(dp) cache the func pc to remove this lookup
-	regs, err := t.Registers()
-	if err != nil {
-		return false
-	}
-	pc := regs.PC()
-	fn := t.BinInfo().PCToFunc(pc)
-	if fn == nil {
-		return false
-	}
-	switch fn.Name {
-	case "runtime.kevent", "runtime.mach_semaphore_wait", "runtime.usleep", "runtime.mach_semaphore_timedwait":
-		return true
-	default:
-		return false
-	}
-}
-
 // Stopped returns whether the thread is stopped at
 // the operating system level.
 func (t *nativeThread) Stopped() bool {
diff --git a/pkg/proc/native/threads_freebsd.go b/pkg/proc/native/threads_freebsd.go
index 6e7cf427..4af26e1b 100644
--- a/pkg/proc/native/threads_freebsd.go
+++ b/pkg/proc/native/threads_freebsd.go
@@ -75,17 +75,6 @@ func (t *nativeThread) singleStep() (err error) {
 	return nil
 }
 
-func (t *nativeThread) Blocked() bool {
-	loc, err := t.Location()
-	if err != nil {
-		return false
-	}
-	if loc.Fn != nil && ((loc.Fn.Name == "runtime.futex") || (loc.Fn.Name == "runtime.usleep") || (loc.Fn.Name == "runtime.clone")) {
-		return true
-	}
-	return false
-}
-
 func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
 	sr := savedRegs.(*fbsdutil.AMD64Registers)
 
diff --git a/pkg/proc/native/threads_linux.go b/pkg/proc/native/threads_linux.go
index 90af3240..d0e978c0 100644
--- a/pkg/proc/native/threads_linux.go
+++ b/pkg/proc/native/threads_linux.go
@@ -71,19 +71,6 @@ func (t *nativeThread) singleStep() (err error) {
 	}
 }
 
-func (t *nativeThread) Blocked() bool {
-	regs, err := t.Registers()
-	if err != nil {
-		return false
-	}
-	pc := regs.PC()
-	fn := t.BinInfo().PCToFunc(pc)
-	if fn != nil && ((fn.Name == "runtime.futex") || (fn.Name == "runtime.usleep") || (fn.Name == "runtime.clone")) {
-		return true
-	}
-	return false
-}
-
 func (t *nativeThread) WriteMemory(addr uint64, data []byte) (written int, err error) {
 	if t.dbp.exited {
 		return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go
index 610b75c4..887a639d 100644
--- a/pkg/proc/native/threads_windows.go
+++ b/pkg/proc/native/threads_windows.go
@@ -111,26 +111,6 @@ func (t *nativeThread) resume() error {
 	return err
 }
 
-func (t *nativeThread) Blocked() bool {
-	// TODO: Probably incorrect - what are the runtime functions that
-	// indicate blocking on Windows?
-	regs, err := t.Registers()
-	if err != nil {
-		return false
-	}
-	pc := regs.PC()
-	fn := t.BinInfo().PCToFunc(pc)
-	if fn == nil {
-		return false
-	}
-	switch fn.Name {
-	case "runtime.kevent", "runtime.usleep":
-		return true
-	default:
-		return false
-	}
-}
-
 // Stopped returns whether the thread is stopped at the operating system
 // level. On windows this always returns true.
 func (t *nativeThread) Stopped() bool {
diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go
index a6d17ade..4bc454ea 100644
--- a/pkg/proc/stack.go
+++ b/pkg/proc/stack.go
@@ -100,7 +100,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
 			return nil, err
 		}
 		so := thread.BinInfo().PCToImage(regs.PC())
-		it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs), 0, nil, -1, nil, 0)
+		it := newStackIterator(thread.BinInfo(), thread.ProcessMemory(), thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs), 0, nil, -1, nil, 0)
 		return it.stacktrace(depth)
 	}
 	return g.Stacktrace(depth, 0)
@@ -120,7 +120,7 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
 		}
 		so := bi.PCToImage(regs.PC())
 		return newStackIterator(
-			bi, g.Thread,
+			bi, g.variable.mem,
 			bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs),
 			g.stack.hi, stkbar, g.stkbarPos, g, opts), nil
 	}
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index 1eafc43c..bef040e1 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -41,6 +41,9 @@ type Target struct {
 	// case only one will be reported.
 	StopReason StopReason
 
+	// currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected.
+	currentThread Thread
+
 	// Goroutine that will be used by default to set breakpoint, eval variables, etc...
 	// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
 	selectedGoroutine *G
@@ -73,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
@@ -108,7 +137,7 @@ func DisableAsyncPreemptEnv() []string {
 }
 
 // NewTarget returns an initialized Target object.
-func NewTarget(p Process, cfg NewTargetConfig) (*Target, error) {
+func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, error) {
 	entryPoint, err := p.EntryPoint()
 	if err != nil {
 		return nil, err
@@ -125,13 +154,14 @@ func NewTarget(p Process, cfg NewTargetConfig) (*Target, error) {
 	}
 
 	t := &Target{
-		Process:    p,
-		proc:       p.(ProcessInternal),
-		fncallForG: make(map[int]*callInjection),
-		StopReason: cfg.StopReason,
+		Process:       p,
+		proc:          p.(ProcessInternal),
+		fncallForG:    make(map[int]*callInjection),
+		StopReason:    cfg.StopReason,
+		currentThread: currentThread,
 	}
 
-	g, _ := GetG(p.CurrentThread())
+	g, _ := GetG(currentThread)
 	t.selectedGoroutine = g
 
 	t.createUnrecoveredPanicBreakpoint()
@@ -171,10 +201,11 @@ func (t *Target) ClearAllGCache() {
 // Restarting of a normal process happens at a higher level (debugger.Restart).
 func (t *Target) Restart(from string) error {
 	t.ClearAllGCache()
-	err := t.proc.Restart(from)
+	currentThread, err := t.proc.Restart(from)
 	if err != nil {
 		return err
 	}
+	t.currentThread = currentThread
 	t.selectedGoroutine, _ = GetG(t.CurrentThread())
 	if from != "" {
 		t.StopReason = StopManual
@@ -210,7 +241,7 @@ func (p *Target) SwitchThread(tid int) error {
 		return err
 	}
 	if th, ok := p.FindThread(tid); ok {
-		p.proc.SetCurrentThread(th)
+		p.currentThread = th
 		p.selectedGoroutine, _ = GetG(p.CurrentThread())
 		return nil
 	}
@@ -247,7 +278,7 @@ func setAsyncPreemptOff(p *Target, v int64) {
 		return
 	}
 	logger := p.BinInfo().logger
-	scope := globalScope(p.BinInfo(), p.BinInfo().Images[0], p.CurrentThread())
+	scope := globalScope(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)
@@ -295,3 +326,10 @@ func (t *Target) createFatalThrowBreakpoint() {
 		}
 	}
 }
+
+// CurrentThread returns the currently selected thread which will be used
+// for next/step/stepout and for reading variables, unless a goroutine is
+// selected.
+func (t *Target) CurrentThread() Thread {
+	return t.currentThread
+}
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index 30102819..9aad77c5 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -142,7 +142,7 @@ func (dbp *Target) Continue() error {
 				if !arch.BreakInstrMovesPC() {
 					bpsize := arch.BreakpointSize()
 					bp := make([]byte, bpsize)
-					_, err = dbp.CurrentThread().ReadMemory(bp, loc.PC)
+					_, err = dbp.Memory().ReadMemory(bp, loc.PC)
 					if bytes.Equal(bp, arch.BreakpointInstruction()) {
 						curthread.SetPC(loc.PC + uint64(bpsize))
 					}
@@ -254,7 +254,7 @@ func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmIn
 		return nil, err
 	}
 	pc := regs.PC() + uint64(off)
-	return disassemble(thread, regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()), true)
+	return disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()), true)
 }
 
 // stepInstructionOut repeatedly calls StepInstruction until the current
@@ -290,12 +290,8 @@ func (dbp *Target) Step() (err error) {
 	}
 
 	if err = next(dbp, true, false); err != nil {
-		switch err.(type) {
-		case ErrThreadBlocked: // Noop
-		default:
-			dbp.ClearInternalBreakpoints()
-			return
-		}
+		_ = dbp.ClearInternalBreakpoints()
+		return err
 	}
 
 	if bp := dbp.CurrentThread().Breakpoint().Breakpoint; bp != nil && bp.Kind == StepBreakpoint && dbp.GetDirection() == Backward {
@@ -489,10 +485,8 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
 
 	ext := filepath.Ext(topframe.Current.File)
 	csource := ext != ".go" && ext != ".s"
-	var thread MemoryReadWriter = curthread
 	var regs Registers
 	if selg != nil && selg.Thread != nil {
-		thread = selg.Thread
 		regs, err = selg.Thread.Registers()
 		if err != nil {
 			return err
@@ -518,13 +512,13 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
 			return nil
 		}
 
-		topframe.Ret, err = findCallInstrForRet(dbp, thread, topframe.Ret, retframe.Current.Fn)
+		topframe.Ret, err = findCallInstrForRet(dbp, dbp.Memory(), topframe.Ret, retframe.Current.Fn)
 		if err != nil {
 			return err
 		}
 	}
 
-	text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false)
+	text, err := disassemble(dbp.Memory(), regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false)
 	if err != nil && stepInto {
 		return err
 	}
@@ -812,7 +806,7 @@ func skipAutogeneratedWrappersIn(p Process, startfn *Function, startpc uint64) (
 			// can't exit Go
 			return startfn, startpc
 		}
-		text, err := Disassemble(p.CurrentThread(), nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End)
+		text, err := Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End)
 		if err != nil {
 			break
 		}
@@ -876,9 +870,6 @@ func skipAutogeneratedWrappersOut(g *G, thread Thread, startTopframe, startRetfr
 	var err error
 	var frames []Stackframe
 	if g == nil {
-		if thread.Blocked() {
-			return
-		}
 		frames, err = ThreadStacktrace(thread, maxSkipAutogeneratedWrappers)
 	} else {
 		frames, err = g.Stacktrace(maxSkipAutogeneratedWrappers, 0)
@@ -961,7 +952,7 @@ func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr
 		curthread = selg.Thread
 	}
 
-	callerText, err := disassemble(curthread, nil, p.Breakpoints(), p.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false)
+	callerText, err := disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false)
 	if err != nil {
 		return err
 	}
@@ -969,9 +960,7 @@ func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr
 
 	var frames []Stackframe
 	if selg == nil {
-		if !curthread.Blocked() {
-			frames, err = ThreadStacktrace(curthread, 3)
-		}
+		frames, err = ThreadStacktrace(curthread, 3)
 	} else {
 		frames, err = selg.Stacktrace(3, 0)
 	}
@@ -985,14 +974,14 @@ func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr
 		if len(frames) < 4 || frames[3].Current.Fn == nil {
 			return &ErrNoSourceForPC{frames[2].Current.PC}
 		}
-		callpc, err = findCallInstrForRet(p, curthread, frames[2].Ret, frames[3].Current.Fn)
+		callpc, err = findCallInstrForRet(p, p.Memory(), frames[2].Ret, frames[3].Current.Fn)
 		if err != nil {
 			return err
 		}
 	} else if ok, pc := isDeferReturnCall(frames, deferReturns); ok {
 		callpc = pc
 	} else {
-		callpc, err = findCallInstrForRet(p, curthread, topframe.Ret, retframe.Current.Fn)
+		callpc, err = findCallInstrForRet(p, p.Memory(), topframe.Ret, retframe.Current.Fn)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go
index 8ca17ff3..e08697c2 100644
--- a/pkg/proc/threads.go
+++ b/pkg/proc/threads.go
@@ -6,7 +6,6 @@ import (
 
 // Thread represents a thread.
 type Thread interface {
-	MemoryReadWriter
 	Location() (*Location, error)
 	// Breakpoint will return the breakpoint that this thread is stopped at or
 	// nil if the thread is not stopped at any breakpoint.
@@ -24,9 +23,9 @@ type Thread interface {
 	// RestoreRegisters restores saved registers
 	RestoreRegisters(Registers) error
 	BinInfo() *BinaryInfo
+	// ProcessMemory returns the process memory.
+	ProcessMemory() MemoryReadWriter
 	StepInstruction() error
-	// Blocked returns true if the thread is blocked
-	Blocked() bool
 	// SetCurrentBreakpoint updates the current breakpoint of this thread, if adjustPC is true also checks for breakpoints that were just hit (this should only be passed true after a thread resume)
 	SetCurrentBreakpoint(adjustPC bool) error
 	// Common returns the CommonThread structure for this thread
@@ -47,14 +46,6 @@ type Location struct {
 	Fn   *Function
 }
 
-// ErrThreadBlocked is returned when the thread
-// is blocked in the scheduler.
-type ErrThreadBlocked struct{}
-
-func (tbe ErrThreadBlocked) Error() string {
-	return "thread blocked"
-}
-
 // CommonThread contains fields used by this package, common to all
 // implementations of the Thread interface.
 type CommonThread struct {
@@ -75,9 +66,6 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
 	var err error
 
 	if g == nil {
-		if thread.Blocked() {
-			return Stackframe{}, Stackframe{}, ErrThreadBlocked{}
-		}
 		frames, err = ThreadStacktrace(thread, 1)
 	} else {
 		frames, err = g.Stacktrace(1, StacktraceReadDefers)
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index d6ecd576..4caf6d72 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -310,16 +310,13 @@ func GoroutinesInfo(dbp *Target, start, count int) ([]*G, int, error) {
 
 	threads := dbp.ThreadList()
 	for _, th := range threads {
-		if th.Blocked() {
-			continue
-		}
 		g, _ := GetG(th)
 		if g != nil {
 			threadg[g.ID] = g
 		}
 	}
 
-	allgptr, allglen, err := dbp.gcache.getRuntimeAllg(dbp.BinInfo(), dbp.CurrentThread())
+	allgptr, allglen, err := dbp.gcache.getRuntimeAllg(dbp.BinInfo(), dbp.Memory())
 	if err != nil {
 		return nil, -1, err
 	}
@@ -434,7 +431,7 @@ func getGVariable(thread Thread) (*Variable, error) {
 	gaddr, hasgaddr := regs.GAddr()
 	if !hasgaddr {
 		var err error
-		gaddr, err = readUintRaw(thread, regs.TLS()+thread.BinInfo().GStructOffset(), int64(thread.BinInfo().Arch.PtrSize()))
+		gaddr, err = readUintRaw(thread.ProcessMemory(), regs.TLS()+thread.BinInfo().GStructOffset(), int64(thread.BinInfo().Arch.PtrSize()))
 		if err != nil {
 			return nil, err
 		}
@@ -568,7 +565,7 @@ func globalScope(bi *BinaryInfo, image *Image, mem MemoryReadWriter) *EvalScope
 }
 
 func newVariableFromThread(t Thread, name string, addr uint64, dwarfType godwarf.Type) *Variable {
-	return newVariable(name, addr, dwarfType, t.BinInfo(), t)
+	return newVariable(name, addr, dwarfType, t.BinInfo(), t.ProcessMemory())
 }
 
 func (v *Variable) newVariable(name string, addr uint64, dwarfType godwarf.Type, mem MemoryReadWriter) *Variable {
@@ -922,7 +919,7 @@ 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.CurrentThread())
+	scope := globalScope(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/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 b4c8398f..6e4ced70 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -381,9 +381,8 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
 	}
 
 	var regs proc.Registers
-	var mem proc.MemoryReadWriter = p.CurrentThread()
+	mem := p.Memory()
 	if g != nil && g.Thread != nil {
-		mem = g.Thread
 		regs, _ = g.Thread.Registers()
 	}
 	instructions, err := proc.Disassemble(mem, regs, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End)
@@ -1374,7 +1373,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.BinInfo(), d.target.CurrentThread(), nil, rawlocs[i:]...)
+			scope := proc.FrameToScope(d.target.BinInfo(), d.target.Memory(), nil, rawlocs[i:]...)
 			locals, err := scope.LocalVariables(*cfg)
 			if err != nil {
 				return nil, err
@@ -1502,7 +1501,7 @@ func (d *Debugger) Disassemble(goroutineID int, addr1, addr2 uint64) ([]proc.Asm
 	}
 	regs, _ := curthread.Registers()
 
-	return proc.Disassemble(curthread, regs, d.target.Breakpoints(), d.target.BinInfo(), addr1, addr2)
+	return proc.Disassemble(d.target.Memory(), regs, d.target.Breakpoints(), d.target.BinInfo(), addr1, addr2)
 }
 
 func (d *Debugger) AsmInstructionText(inst *proc.AsmInstruction, flavour proc.AssemblyFlavour) string {
@@ -1518,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()
@@ -1554,9 +1571,9 @@ func (d *Debugger) ExamineMemory(address uint64, length int) ([]byte, error) {
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
 
-	thread := d.target.CurrentThread()
+	mem := d.target.Memory()
 	data := make([]byte, length)
-	n, err := thread.ReadMemory(data, address)
+	n, err := mem.ReadMemory(data, address)
 	if err != nil {
 		return nil, err
 	}


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/proc/core/core_test.go b/pkg/proc/core/core_test.go
index 43e1ebcd..5a0dca5c 100644
--- a/pkg/proc/core/core_test.go
+++ b/pkg/proc/core/core_test.go
@@ -244,7 +244,7 @@ func TestCore(t *testing.T) {
 	if mainFrame == nil {
 		t.Fatalf("Couldn't find main in stack %v", panickingStack)
 	}
-	msg, err := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64})
+	msg, err := proc.FrameToScope(p.BinInfo(), p.Memory(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64})
 	if err != nil {
 		t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err)
 	}
@@ -371,7 +371,7 @@ mainSearch:
 		t.Fatal("could not find main.main frame")
 	}
 
-	scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, *mainFrame)
+	scope := proc.FrameToScope(p.BinInfo(), 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/proc_test.go b/pkg/proc/proc_test.go
index ad2720b9..f33ddac3 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -359,7 +359,7 @@ func TestClearBreakpointBreakpoint(t *testing.T) {
 		_, err := p.ClearBreakpoint(bp.Addr)
 		assertNoError(err, t, "ClearBreakpoint()")
 
-		data, err := dataAtAddr(p.CurrentThread(), bp.Addr)
+		data, err := dataAtAddr(p.Memory(), bp.Addr)
 		assertNoError(err, t, "dataAtAddr")
 
 		int3 := []byte{0xcc}
@@ -949,19 +949,6 @@ func TestStacktraceGoroutine(t *testing.T) {
 		{{10, "main.agoroutine"}},
 	}
 
-	tgtAgoroutineCount := 10
-
-	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-		// We try to make sure that all goroutines are stopped at a sensible place
-		// before reading their stacktrace, but due to the nature of the test
-		// program there is no guarantee that we always find them in a reasonable
-		// state.
-		// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-		// unnecessary flakiness reduce the target count to 9, allowing one
-		// goroutine to be in a bad state.
-		tgtAgoroutineCount = 9
-	}
-
 	protest.AllowRecording(t)
 	withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) {
 		bp := setFunctionBreakpoint(p, t, "main.stacktraceme")
@@ -1011,7 +998,7 @@ func TestStacktraceGoroutine(t *testing.T) {
 			t.Fatalf("Main goroutine stack not found %d", mainCount)
 		}
 
-		if agoroutineCount < tgtAgoroutineCount {
+		if agoroutineCount < 10 {
 			t.Fatalf("Goroutine stacks not found (%d)", agoroutineCount)
 		}
 
@@ -1154,7 +1141,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.BinInfo(), p.CurrentThread(), nil, frame)
+			scope = proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame)
 		}
 	} else {
 		scope, err = proc.GoroutineScope(p.CurrentThread())
@@ -2531,7 +2518,7 @@ func TestStepOnCallPtrInstr(t *testing.T) {
 			regs, err := p.CurrentThread().Registers()
 			assertNoError(err, t, "Registers()")
 			pc := regs.PC()
-			text, err := proc.Disassemble(p.CurrentThread(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()))
+			text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()))
 			assertNoError(err, t, "Disassemble()")
 			if text[0].IsCall() {
 				found = true
@@ -3008,9 +2995,6 @@ func TestIssue893(t *testing.T) {
 		if _, ok := err.(*frame.ErrNoFDEForPC); ok {
 			return
 		}
-		if _, ok := err.(proc.ErrThreadBlocked); ok {
-			return
-		}
 		if _, ok := err.(*proc.ErrNoSourceForPC); ok {
 			return
 		}
@@ -3041,7 +3025,7 @@ func TestIssue871(t *testing.T) {
 			var frame proc.Stackframe
 			frame, err = findFirstNonRuntimeFrame(p)
 			if err == nil {
-				scope = proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame)
+				scope = proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame)
 			}
 		} else {
 			scope, err = proc.GoroutineScope(p.CurrentThread())
@@ -3488,7 +3472,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.BinInfo(), p.CurrentThread(), nil, frames[2:]...)
+		scope := proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frames[2:]...)
 		args, _ := scope.FunctionArguments(normalLoadConfig)
 		assertNoError(err, t, "FunctionArguments()")
 		if len(args) > 0 {
@@ -3663,7 +3647,7 @@ func TestDisassembleGlobalVars(t *testing.T) {
 	withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
 		mainfn := p.BinInfo().LookupFunc["main.main"]
 		regs, _ := p.CurrentThread().Registers()
-		text, err := proc.Disassemble(p.CurrentThread(), regs, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End)
+		text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End)
 		assertNoError(err, t, "Disassemble")
 		found := false
 		for i := range text {
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index 78ad1a61..43ae875b 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -342,19 +342,6 @@ func TestScopePrefix(t *testing.T) {
 	const goroutinesCurLinePrefix = "* Goroutine "
 	test.AllowRecording(t)
 
-	tgtAgoroutineCount := 10
-
-	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-		// We try to make sure that all goroutines are stopped at a sensible place
-		// before reading their stacktrace, but due to the nature of the test
-		// program there is no guarantee that we always find them in a reasonable
-		// state.
-		// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-		// unnecessary flakiness reduce the target count to 9, allowing one
-		// goroutine to be in a bad state.
-		tgtAgoroutineCount = 9
-	}
-
 	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
 		term.MustExec("b stacktraceme")
 		term.MustExec("continue")
@@ -407,7 +394,7 @@ func TestScopePrefix(t *testing.T) {
 					}
 				}
 			}
-			if len(agoroutines)+extraAgoroutines < tgtAgoroutineCount {
+			if len(agoroutines)+extraAgoroutines < 10 {
 				t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut)
 			}
 		}
@@ -451,12 +438,8 @@ func TestScopePrefix(t *testing.T) {
 			seen[ival] = true
 		}
 
-		firsterr := tgtAgoroutineCount != 10
-
 		for i := range seen {
-			if firsterr {
-				firsterr = false
-			} else if !seen[i] {
+			if !seen[i] {
 				t.Fatalf("goroutine %d not found", i)
 			}
 		}
@@ -536,21 +519,8 @@ func TestOnPrefix(t *testing.T) {
 			}
 		}
 
-		firsterr := false
-		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-			// We try to make sure that all goroutines are stopped at a sensible place
-			// before reading their stacktrace, but due to the nature of the test
-			// program there is no guarantee that we always find them in a reasonable
-			// state.
-			// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-			// unnecessary flakiness allow a single goroutine to be in a bad state.
-			firsterr = true
-		}
-
 		for i := range seen {
-			if firsterr {
-				firsterr = false
-			} else if !seen[i] {
+			if !seen[i] {
 				t.Fatalf("Goroutine %d not seen\n", i)
 			}
 		}
@@ -608,21 +578,8 @@ func TestOnPrefixLocals(t *testing.T) {
 			}
 		}
 
-		firsterr := false
-		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-			// We try to make sure that all goroutines are stopped at a sensible place
-			// before reading their stacktrace, but due to the nature of the test
-			// program there is no guarantee that we always find them in a reasonable
-			// state.
-			// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-			// unnecessary flakiness allow a single goroutine to be in a bad state.
-			firsterr = true
-		}
-
 		for i := range seen {
-			if firsterr {
-				firsterr = false
-			} else if !seen[i] {
+			if !seen[i] {
 				t.Fatalf("Goroutine %d not seen\n", i)
 			}
 		}
@@ -687,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")
 
@@ -731,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")
@@ -1065,9 +1023,6 @@ func findStarFile(name string) string {
 }
 
 func TestIssue1598(t *testing.T) {
-	if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
-		t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH))
-	}
 	test.MustSupportFunctionCalls(t, testBackend)
 	withTestTerminal("issue1598", t, func(term *FakeTerminal) {
 		term.MustExec("break issue1598.go:5")
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/integration1_test.go b/service/test/integration1_test.go
index 89b5f413..08966c52 100644
--- a/service/test/integration1_test.go
+++ b/service/test/integration1_test.go
@@ -751,23 +751,9 @@ func Test1ClientServer_FullStacktrace(t *testing.T) {
 			}
 		}
 
-		firsterr := false
-		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-			// We try to make sure that all goroutines are stopped at a sensible place
-			// before reading their stacktrace, but due to the nature of the test
-			// program there is no guarantee that we always find them in a reasonable
-			// state.
-			// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-			// unnecessary flakiness allow a single goroutine to be in a bad state.
-			firsterr = true
-		}
 		for i := range found {
 			if !found[i] {
-				if firsterr {
-					firsterr = false
-				} else {
-					t.Fatalf("Goroutine %d not found", i)
-				}
+				t.Fatalf("Goroutine %d not found", i)
 			}
 		}
 
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 858afcbc..fa8b8ed0 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -928,23 +928,9 @@ func TestClientServer_FullStacktrace(t *testing.T) {
 			}
 		}
 
-		firsterr := false
-		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-			// We try to make sure that all goroutines are stopped at a sensible place
-			// before reading their stacktrace, but due to the nature of the test
-			// program there is no guarantee that we always find them in a reasonable
-			// state.
-			// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-			// unnecessary flakiness allow a single goroutine to be in a bad state.
-			firsterr = true
-		}
 		for i := range found {
 			if !found[i] {
-				if firsterr {
-					firsterr = false
-				} else {
-					t.Fatalf("Goroutine %d not found", i)
-				}
+				t.Fatalf("Goroutine %d not found", i)
 			}
 		}
 
@@ -1730,9 +1716,6 @@ func mustHaveDebugCalls(t *testing.T, c service.Client) {
 }
 
 func TestClientServerFunctionCall(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
-	}
 	protest.MustSupportFunctionCalls(t, testBackend)
 	withTestClient2("fncall", t, func(c service.Client) {
 		mustHaveDebugCalls(t, c)
@@ -1764,9 +1747,6 @@ func TestClientServerFunctionCall(t *testing.T) {
 }
 
 func TestClientServerFunctionCallBadPos(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
-	}
 	protest.MustSupportFunctionCalls(t, testBackend)
 	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) {
 		t.Skip("this is a safe point for Go 1.12")
@@ -1794,9 +1774,6 @@ func TestClientServerFunctionCallBadPos(t *testing.T) {
 }
 
 func TestClientServerFunctionCallPanic(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
-	}
 	protest.MustSupportFunctionCalls(t, testBackend)
 	withTestClient2("fncall", t, func(c service.Client) {
 		mustHaveDebugCalls(t, c)
@@ -1823,9 +1800,6 @@ func TestClientServerFunctionCallPanic(t *testing.T) {
 }
 
 func TestClientServerFunctionCallStacktrace(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
-	}
 	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 		t.Skip("Go 1.15 executes function calls in a different goroutine so the stack trace will not contain main.main or runtime.main")
 	}
@@ -2104,14 +2078,19 @@ func TestIssue2162(t *testing.T) {
 		t.Skip("skip it for stepping into one place where no source for pc when on pie mode or windows")
 	}
 	withTestClient2("issue2162", t, func(c service.Client) {
-		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main"})
+		state, err := c.GetState()
+		assertNoError(err, t, "GetState()")
+		if state.CurrentThread.Function == nil {
+			// Can't call Step if we don't have the source code of the current function
+			return
+		}
+
+		_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main"})
 		if err != nil {
 			t.Fatalf("Unexpected error: %v", err)
 		}
 
 		_, err = c.Step()
-		if err != nil {
-			assertNoError(err, t, "Step()")
-		}
+		assertNoError(err, t, "Step()")
 	})
 }
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index a196ed96..3860b700 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -91,7 +91,7 @@ func evalScope(p *proc.Target) (*proc.EvalScope, error) {
 	if err != nil {
 		return nil, err
 	}
-	return proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame), nil
+	return proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame), nil
 }
 
 func evalVariable(p *proc.Target, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
@@ -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},
@@ -468,7 +469,7 @@ func TestLocalVariables(t *testing.T) {
 				var frame proc.Stackframe
 				frame, err = findFirstNonRuntimeFrame(p)
 				if err == nil {
-					scope = proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame)
+					scope = proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame)
 				}
 			} else {
 				scope, err = proc.GoroutineScope(p.CurrentThread())
@@ -1150,9 +1151,6 @@ type testCaseCallFunction struct {
 }
 
 func TestCallFunction(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support CallFunction for now")
-	}
 	protest.MustSupportFunctionCalls(t, testBackend)
 
 	var testcases = []testCaseCallFunction{