Repository: go-delve/delve
Pull request #2550 (2021-06-23 22:14:50)

service/dap: set hit breakpoint ids
By setting the breakpoint id for the breakpoint that was hit, the dap client can show the user
which breakpoint was hit by this stopped event. VS Code does this by shifting focus in the
breakpoints view to the breakpoint with the returned id.


Patch:
diff --git a/.gitignore b/.gitignore
index b117424d..f6073ada 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ localtests
 .idea
 *.iml
 .teamcity/target
+.vscode
diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md
index cf1984e3..96ad677b 100644
--- a/Documentation/cli/README.md
+++ b/Documentation/cli/README.md
@@ -353,18 +353,62 @@ Aliases: gr
 ## goroutines
 List program goroutines.
 
-	goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)|-s (start location)] [-t (stack trace)] [-l (labels)]
+	goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument]
 
 Print out info for every goroutine. The flag controls what information is shown along with each goroutine:
 
-	-u	displays location of topmost stackframe in user code
+	-u	displays location of topmost stackframe in user code (default)
 	-r	displays location of topmost stackframe (including frames inside private runtime functions)
 	-g	displays location of go instruction that created the goroutine
 	-s	displays location of the start function
-	-t	displays goroutine's stacktrace
+	-t	displays goroutine's stacktrace (an optional depth value can be specified, default: 10)
 	-l	displays goroutine's labels
 
-If no flag is specified the default is -u.
+If no flag is specified the default is -u, i.e. the first frame within the first 30 frames that is not executing a runtime private function.
+
+FILTERING
+
+If -with or -without are specified only goroutines that match the given condition are returned.
+
+To only display goroutines where the specified location contains (or does not contain, for -without and -wo) expr as a substring, use:
+
+	goroutines -with (userloc|curloc|goloc|startloc) expr
+	goroutines -w (userloc|curloc|goloc|startloc) expr
+	goroutines -without (userloc|curloc|goloc|startloc) expr
+	goroutines -wo (userloc|curloc|goloc|startloc) expr
+	
+To only display goroutines that have (or do not have) the specified label key and value, use:
+	
+
+	goroutines -with label key=value
+	goroutines -without label key=value
+	
+To only display goroutines that have (or do not have) the specified label key, use:
+
+	goroutines -with label key
+	goroutines -without label key
+	
+To only display goroutines that are running (or are not running) on a OS thread, use:
+
+
+	goroutines -with running
+	goroutines -without running
+	
+To only display user (or runtime) goroutines, use:
+
+	goroutines -with user
+	goroutines -without user
+
+GROUPING
+
+	goroutines -group (userloc|curloc|goloc|startloc|running|user)
+
+Groups goroutines by the given location, running status or user classification, up to 5 goroutines per group will be displayed as well as the total number of goroutines in the group.
+
+	goroutines -group label key
+
+Groups goroutines by the value of the label with the specified key.
+
 
 Aliases: grs
 
diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md
index ee60d5dd..b797bed8 100644
--- a/Documentation/cli/starlark.md
+++ b/Documentation/cli/starlark.md
@@ -45,7 +45,7 @@ checkpoints() | Equivalent to API call [ListCheckpoints](https://godoc.org/githu
 dynamic_libraries() | Equivalent to API call [ListDynamicLibraries](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListDynamicLibraries)
 function_args(Scope, Cfg) | Equivalent to API call [ListFunctionArgs](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctionArgs)
 functions(Filter) | Equivalent to API call [ListFunctions](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctions)
-goroutines(Start, Count) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines)
+goroutines(Start, Count, Filters, GoroutineGroupingOptions) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines)
 local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars)
 package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars)
 packages_build_info(IncludeFiles) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo)
diff --git a/Documentation/usage/dlv_dap.md b/Documentation/usage/dlv_dap.md
index e194c6b9..3a87a251 100644
--- a/Documentation/usage/dlv_dap.md
+++ b/Documentation/usage/dlv_dap.md
@@ -13,10 +13,9 @@ to be launched or process to be attached to. The following modes are supported:
 - launch + debug (builds and launches, like 'dlv debug')
 - launch + test (builds and tests, like 'dlv test')
 - attach + local (attaches to a running process, like 'dlv attach')
-The server does not yet support asynchronous request-response communication, so features
-like pausing or setting breakpoints while the program is running are not yet available.
-The server does not accept multiple client connections (--accept-multiclient),
-a feature that is often relied on to enable --continue with remote debugging.
+The server does not yet accept multiple client connections (--accept-multiclient).
+While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if
+execution is resumed at the start of the debug session.
 
 ```
 dlv dap
diff --git a/_fixtures/fatalerror.go b/_fixtures/fatalerror.go
new file mode 100644
index 00000000..ad1e9bfa
--- /dev/null
+++ b/_fixtures/fatalerror.go
@@ -0,0 +1,6 @@
+package main
+
+func main() {
+	var f func()
+	go f()
+}
diff --git a/_fixtures/goroutinegroup.go b/_fixtures/goroutinegroup.go
new file mode 100644
index 00000000..7b5bad86
--- /dev/null
+++ b/_fixtures/goroutinegroup.go
@@ -0,0 +1,75 @@
+package main
+
+import (
+	"context"
+	"runtime"
+	"runtime/pprof"
+	"sync"
+	"time"
+)
+
+func sleepyfunc(wg *sync.WaitGroup, lbl string) {
+	defer wg.Done()
+	pprof.SetGoroutineLabels(pprof.WithLabels(context.Background(), pprof.Labels("name", lbl)))
+	time.Sleep(10 * 60 * time.Second)
+}
+
+func gopoint1(wg *sync.WaitGroup, lbl string, f func(*sync.WaitGroup, string)) {
+	go f(wg, lbl)
+}
+
+func gopoint2(wg *sync.WaitGroup, lbl string, f func(*sync.WaitGroup, string)) {
+	go f(wg, lbl)
+}
+
+func gopoint3(wg *sync.WaitGroup, lbl string, f func(*sync.WaitGroup, string)) {
+	go f(wg, lbl)
+}
+
+func gopoint4(wg *sync.WaitGroup, lbl string, f func(*sync.WaitGroup, string)) {
+	go f(wg, lbl)
+}
+
+func gopoint5(wg *sync.WaitGroup, lbl string, f func(*sync.WaitGroup, string)) {
+	go f(wg, lbl)
+}
+
+func startpoint1(wg *sync.WaitGroup, lbl string) {
+	sleepyfunc(wg, lbl)
+}
+
+func startpoint2(wg *sync.WaitGroup, lbl string) {
+	sleepyfunc(wg, lbl)
+}
+
+func startpoint3(wg *sync.WaitGroup, lbl string) {
+	sleepyfunc(wg, lbl)
+}
+
+func startpoint4(wg *sync.WaitGroup, lbl string) {
+	sleepyfunc(wg, lbl)
+}
+
+func startpoint5(wg *sync.WaitGroup, lbl string) {
+	sleepyfunc(wg, lbl)
+}
+
+func main() {
+	var wg sync.WaitGroup
+
+	for _, lbl := range []string{"one", "two", "three", "four", "five"} {
+		for _, f := range []func(*sync.WaitGroup, string){startpoint1, startpoint2, startpoint3, startpoint4, startpoint5} {
+			for i := 0; i < 1000; i++ {
+				wg.Add(5)
+				gopoint1(&wg, lbl, f)
+				gopoint2(&wg, lbl, f)
+				gopoint3(&wg, lbl, f)
+				gopoint4(&wg, lbl, f)
+				gopoint5(&wg, lbl, f)
+			}
+		}
+	}
+	time.Sleep(2 * time.Second)
+	runtime.Breakpoint()
+	wg.Wait()
+}
diff --git a/_fixtures/morestringarg.go b/_fixtures/morestringarg.go
new file mode 100644
index 00000000..88827354
--- /dev/null
+++ b/_fixtures/morestringarg.go
@@ -0,0 +1,11 @@
+package main
+
+import "fmt"
+
+func f(s, q string) {
+	fmt.Println(s)
+}
+
+func main() {
+	f("very long string 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789X", "very long string B 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789X2")
+}
diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go
index 1f0c8a8e..e063c728 100644
--- a/cmd/dlv/cmds/commands.go
+++ b/cmd/dlv/cmds/commands.go
@@ -183,10 +183,9 @@ to be launched or process to be attached to. The following modes are supported:
 - launch + debug (builds and launches, like 'dlv debug')
 - launch + test (builds and tests, like 'dlv test')
 - attach + local (attaches to a running process, like 'dlv attach')
-The server does not yet support asynchronous request-response communication, so features
-like pausing or setting breakpoints while the program is running are not yet available.
-The server does not accept multiple client connections (--accept-multiclient),
-a feature that is often relied on to enable --continue with remote debugging.`,
+The server does not yet accept multiple client connections (--accept-multiclient).
+While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if
+execution is resumed at the start of the debug session.`,
 		Run: dapCmd,
 	}
 	rootCommand.AddCommand(dapCommand)
diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go
index 3c264c0c..761e6e2a 100644
--- a/pkg/proc/breakpoints.go
+++ b/pkg/proc/breakpoints.go
@@ -272,9 +272,9 @@ func evalBreakpointCondition(thread Thread, cond ast.Expr) (bool, error) {
 	if cond == nil {
 		return true, nil
 	}
-	scope, err := GoroutineScope(thread)
+	scope, err := GoroutineScope(nil, thread)
 	if err != nil {
-		scope, err = ThreadScope(thread)
+		scope, err = ThreadScope(nil, thread)
 		if err != nil {
 			return true, err
 		}
@@ -568,7 +568,7 @@ func configureReturnBreakpoint(bi *BinaryInfo, bp *Breakpoint, topframe *Stackfr
 	}
 }
 
-func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
+func (rbpi *returnBreakpointInfo) Collect(t *Target, thread Thread) []*Variable {
 	if rbpi == nil {
 		return nil
 	}
@@ -577,7 +577,7 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
 	if err != nil {
 		return returnInfoError("could not get g", err, thread.ProcessMemory())
 	}
-	scope, err := GoroutineScope(thread)
+	scope, err := GoroutineScope(t, thread)
 	if err != nil {
 		return returnInfoError("could not get scope", err, thread.ProcessMemory())
 	}
diff --git a/pkg/proc/core/minidump/minidump.go b/pkg/proc/core/minidump/minidump.go
index 9348f3cf..65e86a18 100644
--- a/pkg/proc/core/minidump/minidump.go
+++ b/pkg/proc/core/minidump/minidump.go
@@ -7,7 +7,7 @@ package minidump
 // ProcDump utility.
 //
 // The file format is described on MSDN starting at:
-//  https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_header
+//  https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_header
 // which is the structure found at offset 0 on a minidump file.
 //
 // Further information on the format can be found reading
@@ -120,7 +120,7 @@ type Minidump struct {
 }
 
 // Stream represents one (uninterpreted) stream in a minidump file.
-// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_directory
+// See: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_directory
 type Stream struct {
 	Type    StreamType
 	Offset  int
@@ -128,7 +128,7 @@ type Stream struct {
 }
 
 // Thread represents an entry in the ThreadList stream.
-// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_thread
+// See: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread
 type Thread struct {
 	ID            uint32
 	SuspendCount  uint32
@@ -139,7 +139,7 @@ type Thread struct {
 }
 
 // Module represents an entry in the ModuleList stream.
-// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_module
+// See: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_module
 type Module struct {
 	BaseOfImage   uint64
 	SizeOfImage   uint32
@@ -156,7 +156,7 @@ type Module struct {
 }
 
 // VSFixedFileInfo: Visual Studio Fixed File Info.
-// See: https://docs.microsoft.com/en-us/windows/desktop/api/verrsrc/ns-verrsrc-tagvs_fixedfileinfo
+// See: https://docs.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
 type VSFixedFileInfo struct {
 	Signature        uint32
 	StructVersion    uint32
@@ -194,7 +194,7 @@ func (m *MemoryRange) ReadMemory(buf []byte, addr uint64) (int, error) {
 }
 
 // MemoryInfo reprents an entry in the MemoryInfoList stream.
-// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory_info
+// See: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory_info_list
 type MemoryInfo struct {
 	Addr       uint64
 	Size       uint64
@@ -596,8 +596,8 @@ func readModuleList(mdmp *Minidump, buf *minidumpBuf) {
 
 // readMemory64List reads a _MINIDUMP_MEMORY64_LIST structure, containing
 // the description of the process memory.
-// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory64_list
-// And: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory_descriptor
+// See: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory64_list
+// And: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory_descriptor
 func readMemory64List(mdmp *Minidump, buf *minidumpBuf, logfn func(fmt string, args ...interface{})) {
 	rangesNum := buf.u64()
 	baseOff := int(buf.u64())
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index 6d33ec05..d4f993d8 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -32,6 +32,7 @@ type EvalScope struct {
 	Mem     MemoryReadWriter // Target's memory
 	g       *G
 	BinInfo *BinaryInfo
+	target  *Target
 
 	frameOffset int64
 
@@ -67,16 +68,18 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error
 	if err != nil {
 		return nil, err
 	}
-	if g == nil {
-		return ThreadScope(ct)
-	}
 
 	var opts StacktraceOptions
 	if deferCall > 0 {
 		opts = StacktraceReadDefers
 	}
 
-	locs, err := g.Stacktrace(frame+1, opts)
+	var locs []Stackframe
+	if g != nil {
+		locs, err = g.Stacktrace(frame+1, opts)
+	} else {
+		locs, err = ThreadStacktrace(ct, frame+1)
+	}
 	if err != nil {
 		return nil, err
 	}
@@ -95,10 +98,10 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error
 			return nil, d.Unreadable
 		}
 
-		return d.EvalScope(ct)
+		return d.EvalScope(dbp, ct)
 	}
 
-	return FrameToScope(dbp.BinInfo(), dbp.Memory(), g, locs[frame:]...), nil
+	return FrameToScope(dbp, dbp.BinInfo(), dbp.Memory(), g, locs[frame:]...), nil
 }
 
 // FrameToScope returns a new EvalScope for frames[0].
@@ -106,7 +109,7 @@ func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error
 // frames[0].Regs.SP() and frames[1].Regs.CFA will be cached.
 // Otherwise all memory between frames[0].Regs.SP() and frames[0].Regs.CFA
 // will be cached.
-func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope {
+func FrameToScope(t *Target, bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope {
 	// Creates a cacheMem that will preload the entire stack frame the first
 	// time any local variable is read.
 	// Remember that the stack grows downward in memory.
@@ -121,13 +124,13 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
 		thread = cacheMemory(thread, minaddr, int(maxaddr-minaddr))
 	}
 
-	s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, g: g, BinInfo: bi, frameOffset: frames[0].FrameOffset()}
+	s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, g: g, BinInfo: bi, target: t, frameOffset: frames[0].FrameOffset()}
 	s.PC = frames[0].lastpc
 	return s
 }
 
 // ThreadScope returns an EvalScope for the given thread.
-func ThreadScope(thread Thread) (*EvalScope, error) {
+func ThreadScope(t *Target, thread Thread) (*EvalScope, error) {
 	locations, err := ThreadStacktrace(thread, 1)
 	if err != nil {
 		return nil, err
@@ -135,11 +138,11 @@ func ThreadScope(thread Thread) (*EvalScope, error) {
 	if len(locations) < 1 {
 		return nil, errors.New("could not decode first frame")
 	}
-	return FrameToScope(thread.BinInfo(), thread.ProcessMemory(), nil, locations...), nil
+	return FrameToScope(t, thread.BinInfo(), thread.ProcessMemory(), nil, locations...), nil
 }
 
 // GoroutineScope returns an EvalScope for the goroutine running on the given thread.
-func GoroutineScope(thread Thread) (*EvalScope, error) {
+func GoroutineScope(t *Target, thread Thread) (*EvalScope, error) {
 	locations, err := ThreadStacktrace(thread, 1)
 	if err != nil {
 		return nil, err
@@ -151,7 +154,7 @@ func GoroutineScope(thread Thread) (*EvalScope, error) {
 	if err != nil {
 		return nil, err
 	}
-	return FrameToScope(thread.BinInfo(), thread.ProcessMemory(), g, locations...), nil
+	return FrameToScope(t, thread.BinInfo(), thread.ProcessMemory(), g, locations...), nil
 }
 
 // EvalExpression returns the value of the given expression.
@@ -219,7 +222,7 @@ func (scope *EvalScope) Locals() ([]*Variable, error) {
 	vars := make([]*Variable, 0, len(varEntries))
 	depths := make([]int, 0, len(varEntries))
 	for _, entry := range varEntries {
-		val, err := extractVarInfoFromEntry(scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree)
+		val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree)
 		if err != nil {
 			// skip variables that we can't parse yet
 			continue
@@ -472,7 +475,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
 		}
 
 		// Ignore errors trying to extract values
-		val, err := extractVarInfoFromEntry(scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry))
+		val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry))
 		if val != nil && val.Kind == reflect.Invalid {
 			continue
 		}
@@ -509,7 +512,7 @@ func (scope *EvalScope) findGlobalInternal(name string) (*Variable, error) {
 			if err != nil {
 				return nil, err
 			}
-			return extractVarInfoFromEntry(scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry))
+			return extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry))
 		}
 	}
 	for _, fn := range scope.BinInfo.Functions {
@@ -720,7 +723,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
 					if err != nil {
 						return nil, fmt.Errorf("blah: %v", err)
 					}
-					gvar := newVariable("curg", fakeAddress, typ, scope.BinInfo, scope.Mem)
+					gvar := newVariable("curg", fakeAddressUnresolv, typ, scope.BinInfo, scope.Mem)
 					gvar.loaded = true
 					gvar.Flags = VariableFakeAddress
 					gvar.Children = append(gvar.Children, *newConstant(constant.MakeInt64(0), scope.Mem))
@@ -841,7 +844,14 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
 
 		n, _ := constant.Int64Val(argv.Value)
 
-		v.Children = []Variable{*(newVariable("", uint64(n), ttyp.Type, scope.BinInfo, scope.Mem))}
+		mem := scope.Mem
+		if scope.target != nil {
+			if mem2 := scope.target.findFakeMemory(uint64(n)); mem2 != nil {
+				mem = mem2
+			}
+		}
+
+		v.Children = []Variable{*(newVariable("", uint64(n), ttyp.Type, scope.BinInfo, mem))}
 		v.Children[0].OnlyAddr = true
 		return v, nil
 
@@ -1158,9 +1168,9 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
 				v := newVariable(node.Name, 0, typ, scope.BinInfo, scope.Mem)
 				if v.Kind == reflect.String {
 					v.Len = int64(len(reg.Bytes) * 2)
-					v.Base = fakeAddress
+					v.Base = fakeAddressUnresolv
 				}
-				v.Addr = fakeAddress
+				v.Addr = fakeAddressUnresolv
 				v.Flags = VariableCPURegister
 				v.reg = reg
 				return v, nil
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index 3b35c12a..2e4fdf15 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -168,7 +168,7 @@ func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig
 		return errFuncCallUnsupported
 	}
 
-	scope, err := GoroutineScope(g.Thread)
+	scope, err := GoroutineScope(t, g.Thread)
 	if err != nil {
 		return err
 	}
@@ -722,7 +722,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 	switch rax {
 	case debugCallAXPrecheckFailed:
 		// get error from top of the stack and return it to user
-		errvar, err := readTopstackVariable(thread, regs, "string", loadFullValue)
+		errvar, err := readTopstackVariable(p, thread, regs, "string", loadFullValue)
 		if err != nil {
 			fncall.err = fmt.Errorf("could not get precheck error reason: %v", err)
 			break
@@ -797,7 +797,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 		if fncall.panicvar != nil || fncall.lateCallFailure {
 			break
 		}
-		retScope, err := ThreadScope(thread)
+		retScope, err := ThreadScope(p, thread)
 		if err != nil {
 			fncall.err = fmt.Errorf("could not get return values: %v", err)
 			break
@@ -830,7 +830,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 
 	case debugCallAXReadPanic:
 		// read panic value from stack
-		fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", callScope.callCtx.retLoadCfg)
+		fncall.panicvar, err = readTopstackVariable(p, thread, regs, "interface {}", callScope.callCtx.retLoadCfg)
 		if err != nil {
 			fncall.err = fmt.Errorf("could not get panic: %v", err)
 			break
@@ -846,9 +846,9 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 	return false
 }
 
-func readTopstackVariable(thread Thread, regs Registers, typename string, loadCfg LoadConfig) (*Variable, error) {
+func readTopstackVariable(t *Target, thread Thread, regs Registers, typename string, loadCfg LoadConfig) (*Variable, error) {
 	bi := thread.BinInfo()
-	scope, err := ThreadScope(thread)
+	scope, err := ThreadScope(t, thread)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index 12123a63..f42f85c4 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -11,7 +11,7 @@
 // The protocol is specified at:
 //   https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
 // with additional documentation for lldb specific extensions described at:
-//   https://github.com/llvm-mirror/lldb/blob/master/docs/lldb-gdb-remote.txt
+//   https://github.com/llvm/llvm-project/blob/main/lldb/docs/lldb-gdb-remote.txt
 //
 // Terminology:
 //  * inferior: the program we are trying to debug
@@ -82,6 +82,7 @@ import (
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/linutil"
+	"github.com/go-delve/delve/pkg/proc/macutil"
 	isatty "github.com/mattn/go-isatty"
 )
 
@@ -431,6 +432,9 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [
 	if runtime.GOOS == "windows" {
 		return nil, ErrUnsupportedOS
 	}
+	if err := macutil.CheckRosetta(); err != nil {
+		return nil, err
+	}
 
 	foreground := flags&proc.LaunchForeground != 0
 
@@ -548,6 +552,9 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
 	if runtime.GOOS == "windows" {
 		return nil, ErrUnsupportedOS
 	}
+	if err := macutil.CheckRosetta(); err != nil {
+		return nil, err
+	}
 
 	var (
 		isDebugserver bool
@@ -1853,8 +1860,11 @@ func (t *gdbThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
 	gdbreg, ok := t.regs.regs[regName]
 	if !ok && strings.HasPrefix(regName, "xmm") {
 		// XMMn and YMMn are the same amd64 register (in different sizes), if we
-		// don't find XMMn try YMMn instead.
+		// don't find XMMn try YMMn or ZMMn instead.
 		gdbreg, ok = t.regs.regs["y"+regName[1:]]
+		if !ok {
+			gdbreg, ok = t.regs.regs["z"+regName[1:]]
+		}
 	}
 	if !ok {
 		return fmt.Errorf("could not set register %s: not found", regName)
@@ -1913,8 +1923,16 @@ func (regs *gdbRegisters) Slice(floatingPoint bool) ([]proc.Register, error) {
 
 			value := regs.regs[reginfo.Name].value
 			xmmName := "x" + reginfo.Name[1:]
-			r = proc.AppendBytesRegister(r, strings.ToUpper(xmmName), value[:16])
-			r = proc.AppendBytesRegister(r, strings.ToUpper(reginfo.Name), value[16:])
+			r = proc.AppendBytesRegister(r, strings.ToUpper(xmmName), value)
+
+		case reginfo.Bitsize == 512:
+			if !strings.HasPrefix(strings.ToLower(reginfo.Name), "zmm") || !floatingPoint {
+				continue
+			}
+
+			value := regs.regs[reginfo.Name].value
+			xmmName := "x" + reginfo.Name[1:]
+			r = proc.AppendBytesRegister(r, strings.ToUpper(xmmName), value)
 		}
 	}
 	return r, nil
diff --git a/pkg/proc/macutil/rosetta_darwin.go b/pkg/proc/macutil/rosetta_darwin.go
new file mode 100644
index 00000000..85e624eb
--- /dev/null
+++ b/pkg/proc/macutil/rosetta_darwin.go
@@ -0,0 +1,19 @@
+package macutil
+
+import (
+	"errors"
+	"syscall"
+)
+
+// CheckRosetta returns an error if the calling process is being translated
+// by Apple Rosetta.
+func CheckRosetta() error {
+	pt, err := syscall.Sysctl("sysctl.proc_translated")
+	if err != nil {
+		return nil
+	}
+	if len(pt) > 0 && pt[0] == 1 {
+		return errors.New("can not run under Rosetta, check that the installed build of Go is right for your CPU architecture")
+	}
+	return nil
+}
diff --git a/pkg/proc/macutil/rosetta_other.go b/pkg/proc/macutil/rosetta_other.go
new file mode 100644
index 00000000..5df9f941
--- /dev/null
+++ b/pkg/proc/macutil/rosetta_other.go
@@ -0,0 +1,9 @@
+// +build !darwin
+
+package macutil
+
+// CheckRosetta returns an error if the calling process is being translated
+// by Apple Rosetta.
+func CheckRosetta() error {
+	return nil
+}
diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go
index 8b19fbfb..1ed363ea 100644
--- a/pkg/proc/mem.go
+++ b/pkg/proc/mem.go
@@ -76,11 +76,6 @@ func cacheMemory(mem MemoryReadWriter, addr uint64, size int) MemoryReadWriter {
 	return &memCache{false, addr, make([]byte, size), mem}
 }
 
-// fakeAddress used by extractVarInfoFromEntry for variables that do not
-// have a memory address, we can't use 0 because a lot of code (likely
-// including client code) assumes that addr == 0 is nil
-const fakeAddress = 0xbeef0000
-
 // compositeMemory represents a chunk of memory that is stored in CPU
 // registers or non-contiguously.
 //
@@ -89,6 +84,7 @@ const fakeAddress = 0xbeef0000
 // with some fields stored into CPU registers and other fields stored in
 // memory.
 type compositeMemory struct {
+	base    uint64 // base address for this composite memory
 	realmem MemoryReadWriter
 	arch    *Arch
 	regs    op.DwarfRegisters
@@ -133,7 +129,7 @@ func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters
 }
 
 func (mem *compositeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
-	addr -= fakeAddress
+	addr -= mem.base
 	if addr >= uint64(len(mem.data)) || addr+uint64(len(data)) > uint64(len(mem.data)) {
 		return 0, errors.New("read out of bounds")
 	}
@@ -142,7 +138,7 @@ func (mem *compositeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
 }
 
 func (mem *compositeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
-	addr -= fakeAddress
+	addr -= mem.base
 	if addr >= uint64(len(mem.data)) || addr+uint64(len(data)) > uint64(len(mem.data)) {
 		return 0, errors.New("write out of bounds")
 	}
diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go
index 1e533ef9..aaf66ba2 100644
--- a/pkg/proc/native/proc_darwin.go
+++ b/pkg/proc/native/proc_darwin.go
@@ -18,6 +18,7 @@ import (
 	sys "golang.org/x/sys/unix"
 
 	"github.com/go-delve/delve/pkg/proc"
+	"github.com/go-delve/delve/pkg/proc/macutil"
 )
 
 // osProcessDetails holds Darwin specific information.
@@ -51,6 +52,9 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
 	if _, err := os.Stat(argv0Go); err != nil {
 		return nil, err
 	}
+	if err := macutil.CheckRosetta(); err != nil {
+		return nil, err
+	}
 
 	argv0 := C.CString(argv0Go)
 	argvSlice := make([]*C.char, 0, len(cmd)+1)
@@ -130,6 +134,9 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
 
 // Attach to an existing process with the given PID.
 func Attach(pid int, _ []string) (*proc.Target, error) {
+	if err := macutil.CheckRosetta(); err != nil {
+		return nil, err
+	}
 	dbp := newProcess(pid)
 
 	kret := C.acquire_mach_task(C.int(pid),
diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go
index 68a7338a..005bca9e 100644
--- a/pkg/proc/stack.go
+++ b/pkg/proc/stack.go
@@ -620,8 +620,8 @@ func (d *Defer) Next() *Defer {
 // EvalScope returns an EvalScope relative to the argument frame of this deferred call.
 // The argument frame of a deferred call is stored in memory immediately
 // after the deferred header.
-func (d *Defer) EvalScope(thread Thread) (*EvalScope, error) {
-	scope, err := GoroutineScope(thread)
+func (d *Defer) EvalScope(t *Target, thread Thread) (*EvalScope, error) {
+	scope, err := GoroutineScope(t, thread)
 	if err != nil {
 		return nil, fmt.Errorf("could not get scope: %v", err)
 	}
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index fc61a7f0..3f4cfcd5 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -5,8 +5,10 @@ import (
 	"fmt"
 	"go/constant"
 	"os"
+	"sort"
 	"strings"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/goversion"
 )
 
@@ -66,6 +68,12 @@ type Target struct {
 	// exitStatus is the exit status of the process we are debugging.
 	// Saved here to relay to any future commands.
 	exitStatus int
+
+	// fakeMemoryRegistry contains the list of all compositeMemory objects
+	// created since the last restart, it exists so that registerized variables
+	// can be given a unique address.
+	fakeMemoryRegistry    []*compositeMemory
+	fakeMemoryRegistryMap map[string]*compositeMemory
 }
 
 // ErrProcessExited indicates that the process has exited and contains both
@@ -181,6 +189,7 @@ func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, e
 	t.createFatalThrowBreakpoint()
 
 	t.gcache.init(p.BinInfo())
+	t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
 
 	if cfg.DisableAsyncPreempt {
 		setAsyncPreemptOff(t, 1)
@@ -232,9 +241,10 @@ func (t *Target) SupportsFunctionCalls() bool {
 	return t.Process.BinInfo().Arch.Name == "amd64"
 }
 
-// ClearAllGCache clears the internal Goroutine cache.
+// ClearCaches clears internal caches that should not survive a restart.
 // This should be called anytime the target process executes instructions.
-func (t *Target) ClearAllGCache() {
+func (t *Target) ClearCaches() {
+	t.clearFakeMemory()
 	t.gcache.Clear()
 	for _, thread := range t.ThreadList() {
 		thread.Common().g = nil
@@ -245,7 +255,7 @@ func (t *Target) ClearAllGCache() {
 // This is only useful for recorded targets.
 // Restarting of a normal process happens at a higher level (debugger.Restart).
 func (t *Target) Restart(from string) error {
-	t.ClearAllGCache()
+	t.ClearCaches()
 	currentThread, err := t.proc.Restart(from)
 	if err != nil {
 		return err
@@ -385,3 +395,73 @@ func (t *Target) CurrentThread() Thread {
 func (t *Target) SetNextBreakpointID(id int) {
 	t.Breakpoints().breakpointIDCounter = id
 }
+
+const (
+	fakeAddressBase     = 0xbeef000000000000
+	fakeAddressUnresolv = 0xbeed000000000000 // this address never resloves to memory
+)
+
+// newCompositeMemory creates a new compositeMemory object and registers it.
+// If the same composite memory has been created before it will return a
+// cached object.
+// This caching is primarily done so that registerized variables don't get a
+// different address every time they are evaluated, which would be confusing
+// and leak memory.
+func (t *Target) newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece, descr *locationExpr) (int64, *compositeMemory, error) {
+	var key string
+	if regs.CFA != 0 && len(pieces) > 0 {
+		// key is created by concatenating the location expression with the CFA,
+		// this combination is guaranteed to be unique between resumes.
+		buf := new(strings.Builder)
+		fmt.Fprintf(buf, "%#x ", regs.CFA)
+		op.PrettyPrint(buf, descr.instr)
+		key = buf.String()
+
+		if cmem := t.fakeMemoryRegistryMap[key]; cmem != nil {
+			return int64(cmem.base), cmem, nil
+		}
+	}
+
+	cmem, err := newCompositeMemory(mem, t.BinInfo().Arch, regs, pieces)
+	if err != nil {
+		return 0, cmem, err
+	}
+	t.registerFakeMemory(cmem)
+	if key != "" {
+		t.fakeMemoryRegistryMap[key] = cmem
+	}
+	return int64(cmem.base), cmem, nil
+}
+
+func (t *Target) registerFakeMemory(mem *compositeMemory) (addr uint64) {
+	t.fakeMemoryRegistry = append(t.fakeMemoryRegistry, mem)
+	addr = fakeAddressBase
+	if len(t.fakeMemoryRegistry) > 1 {
+		prevMem := t.fakeMemoryRegistry[len(t.fakeMemoryRegistry)-2]
+		addr = uint64(alignAddr(int64(prevMem.base+uint64(len(prevMem.data))), 0x100)) // the call to alignAddr just makes the address look nicer, it is not necessary
+	}
+	mem.base = addr
+	return addr
+}
+
+func (t *Target) findFakeMemory(addr uint64) *compositeMemory {
+	i := sort.Search(len(t.fakeMemoryRegistry), func(i int) bool {
+		mem := t.fakeMemoryRegistry[i]
+		return addr <= mem.base || (mem.base <= addr && addr < (mem.base+uint64(len(mem.data))))
+	})
+	if i != len(t.fakeMemoryRegistry) {
+		mem := t.fakeMemoryRegistry[i]
+		if mem.base <= addr && addr < (mem.base+uint64(len(mem.data))) {
+			return mem
+		}
+	}
+	return nil
+}
+
+func (t *Target) clearFakeMemory() {
+	for i := range t.fakeMemoryRegistry {
+		t.fakeMemoryRegistry[i] = nil
+	}
+	t.fakeMemoryRegistry = t.fakeMemoryRegistry[:0]
+	t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
+}
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index 0106c5b6..3c138b3d 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -68,7 +68,7 @@ func (dbp *Target) Continue() error {
 			dbp.ClearInternalBreakpoints()
 			return nil
 		}
-		dbp.ClearAllGCache()
+		dbp.ClearCaches()
 		trapthread, stopReason, err := dbp.proc.ContinueOnce()
 		dbp.StopReason = stopReason
 		if err != nil {
@@ -182,7 +182,7 @@ func (dbp *Target) Continue() error {
 					return dbp.StepInstruction()
 				}
 			default:
-				curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
+				curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
 				if err := dbp.ClearInternalBreakpoints(); err != nil {
 					return err
 				}
@@ -269,7 +269,7 @@ func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmIn
 // This function is used to step out of runtime.Breakpoint as well as
 // runtime.debugCallV1.
 func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error {
-	defer dbp.ClearAllGCache()
+	defer dbp.ClearCaches()
 	for {
 		if err := curthread.StepInstruction(); err != nil {
 			return err
@@ -415,7 +415,7 @@ func (dbp *Target) StepInstruction() (err error) {
 		}
 		thread = g.Thread
 	}
-	dbp.ClearAllGCache()
+	dbp.ClearCaches()
 	if ok, err := dbp.Valid(); !ok {
 		return err
 	}
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 2190f7e2..82d894ef 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -521,6 +521,20 @@ func (g *G) StartLoc() Location {
 	return Location{PC: g.StartPC, File: f, Line: l, Fn: fn}
 }
 
+// System returns true if g is a system goroutine. See isSystemGoroutine in
+// $GOROOT/src/runtime/traceback.go.
+func (g *G) System() bool {
+	loc := g.StartLoc()
+	if loc.Fn == nil {
+		return false
+	}
+	switch loc.Fn.Name {
+	case "runtime.main", "runtime.handleAsyncEvent", "runtime.runfinq":
+		return false
+	}
+	return strings.HasPrefix(loc.Fn.Name, "runtime.")
+}
+
 func (g *G) Labels() map[string]string {
 	if g.labels != nil {
 		return *g.labels
@@ -1111,7 +1125,7 @@ func readVarEntry(entry *godwarf.Tree, image *Image) (name string, typ godwarf.T
 
 // Extracts the name and type of a variable from a dwarf entry
 // then executes the instructions given in the  DW_AT_location attribute to grab the variable's address
-func extractVarInfoFromEntry(bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree) (*Variable, error) {
+func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree) (*Variable, error) {
 	if entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagVariable {
 		return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", entry.Tag.String())
 	}
@@ -1123,9 +1137,16 @@ func extractVarInfoFromEntry(bi *BinaryInfo, image *Image, regs op.DwarfRegister
 
 	addr, pieces, descr, err := bi.Location(entry, dwarf.AttrLocation, regs.PC(), regs)
 	if pieces != nil {
-		addr = fakeAddress
 		var cmem *compositeMemory
-		cmem, err = newCompositeMemory(mem, bi.Arch, regs, pieces)
+		if tgt != nil {
+			addr, cmem, err = tgt.newCompositeMemory(mem, regs, pieces, descr)
+		} else {
+			cmem, err = newCompositeMemory(mem, bi.Arch, regs, pieces)
+			if cmem != nil {
+				cmem.base = fakeAddressUnresolv
+				addr = int64(cmem.base)
+			}
+		}
 		if cmem != nil {
 			mem = cmem
 		}
@@ -1232,7 +1253,7 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
 
 		case v.Flags&VariableCPURegister != 0:
 			val = fmt.Sprintf("%x", v.reg.Bytes)
-			s := v.Base - fakeAddress
+			s := v.Base - fakeAddressUnresolv
 			if s < uint64(len(val)) {
 				val = val[s:]
 				if v.Len >= 0 && v.Len < int64(len(val)) {
@@ -2282,7 +2303,7 @@ func (v *Variable) registerVariableTypeConv(newtyp string) (*Variable, error) {
 	v.loaded = true
 	v.Kind = reflect.Array
 	v.Len = int64(len(v.Children))
-	v.Base = fakeAddress
+	v.Base = fakeAddressUnresolv
 	v.DwarfType = fakeArrayType(uint64(len(v.Children)), &godwarf.VoidType{CommonType: godwarf.CommonType{ByteSize: int64(n)}})
 	v.RealType = v.DwarfType
 	return v, nil
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index 8c4e36d3..24b08f53 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -220,18 +220,62 @@ If called with the linespec argument it will delete all the breakpoints matching
 toggle <breakpoint name or id>`},
 		{aliases: []string{"goroutines", "grs"}, group: goroutineCmds, cmdFn: goroutines, helpMsg: `List program goroutines.
 
-	goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)|-s (start location)] [-t (stack trace)] [-l (labels)]
+	goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument]
 
 Print out info for every goroutine. The flag controls what information is shown along with each goroutine:
 
-	-u	displays location of topmost stackframe in user code
+	-u	displays location of topmost stackframe in user code (default)
 	-r	displays location of topmost stackframe (including frames inside private runtime functions)
 	-g	displays location of go instruction that created the goroutine
 	-s	displays location of the start function
-	-t	displays goroutine's stacktrace
+	-t	displays goroutine's stacktrace (an optional depth value can be specified, default: 10)
 	-l	displays goroutine's labels
 
-If no flag is specified the default is -u.`},
+If no flag is specified the default is -u, i.e. the first frame within the first 30 frames that is not executing a runtime private function.
+
+FILTERING
+
+If -with or -without are specified only goroutines that match the given condition are returned.
+
+To only display goroutines where the specified location contains (or does not contain, for -without and -wo) expr as a substring, use:
+
+	goroutines -with (userloc|curloc|goloc|startloc) expr
+	goroutines -w (userloc|curloc|goloc|startloc) expr
+	goroutines -without (userloc|curloc|goloc|startloc) expr
+	goroutines -wo (userloc|curloc|goloc|startloc) expr
+	
+To only display goroutines that have (or do not have) the specified label key and value, use:
+	
+
+	goroutines -with label key=value
+	goroutines -without label key=value
+	
+To only display goroutines that have (or do not have) the specified label key, use:
+
+	goroutines -with label key
+	goroutines -without label key
+	
+To only display goroutines that are running (or are not running) on a OS thread, use:
+
+
+	goroutines -with running
+	goroutines -without running
+	
+To only display user (or runtime) goroutines, use:
+
+	goroutines -with user
+	goroutines -without user
+
+GROUPING
+
+	goroutines -group (userloc|curloc|goloc|startloc|running|user)
+
+Groups goroutines by the given location, running status or user classification, up to 5 goroutines per group will be displayed as well as the total number of goroutines in the group.
+
+	goroutines -group label key
+
+Groups goroutines by the value of the label with the specified key.
+`},
 		{aliases: []string{"goroutine", "gr"}, group: goroutineCmds, allowedPrefixes: onPrefix, cmdFn: c.goroutine, helpMsg: `Shows or changes current goroutine
 
 	goroutine
@@ -717,67 +761,116 @@ const (
 	printGoroutinesLabels
 )
 
-func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, flags printGoroutinesFlags, state *api.DebuggerState) error {
+func printGoroutines(t *Term, indent string, gs []*api.Goroutine, fgl formatGoroutineLoc, flags printGoroutinesFlags, depth int, state *api.DebuggerState) error {
 	for _, g := range gs {
-		prefix := "  "
+		prefix := indent + "  "
 		if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
-			prefix = "* "
+			prefix = indent + "* "
 		}
 		fmt.Printf("%sGoroutine %s\n", prefix, t.formatGoroutine(g, fgl))
 		if flags&printGoroutinesLabels != 0 {
-			writeGoroutineLabels(os.Stdout, g, "\t")
+			writeGoroutineLabels(os.Stdout, g, indent+"\t")
 		}
 		if flags&printGoroutinesStack != 0 {
-			stack, err := t.client.Stacktrace(g.ID, 10, 0, nil)
+			stack, err := t.client.Stacktrace(g.ID, depth, 0, nil)
 			if err != nil {
 				return err
 			}
-			printStack(t, os.Stdout, stack, "\t", false)
+			printStack(t, os.Stdout, stack, indent+"\t", false)
 		}
 	}
 	return nil
 }
 
+const (
+	maxGroupMembers    = 5
+	maxGoroutineGroups = 50
+)
+
 func goroutines(t *Term, ctx callContext, argstr string) error {
 	args := strings.Split(argstr, " ")
+	var filters []api.ListGoroutinesFilter
+	var group api.GoroutineGroupingOptions
 	var fgl = fglUserCurrent
 	var flags printGoroutinesFlags
+	var depth = 10
+	var batchSize = goroutineBatchSize
+
+	group.MaxGroupMembers = maxGroupMembers
+	group.MaxGroups = maxGoroutineGroups
+
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+		switch arg {
+		case "-u":
+			fgl = fglUserCurrent
+		case "-r":
+			fgl = fglRuntimeCurrent
+		case "-g":
+			fgl = fglGo
+		case "-s":
+			fgl = fglStart
+		case "-l":
+			flags |= printGoroutinesLabels
+		case "-t":
+			flags |= printGoroutinesStack
+			// optional depth argument
+			if i+1 < len(args) && len(args[i+1]) > 0 {
+				n, err := strconv.Atoi(args[i+1])
+				if err == nil {
+					depth = n
+					i++
+				}
+			}
 
-	switch len(args) {
-	case 0:
-		// nothing to do
-	case 1, 2:
-		for _, arg := range args {
-			switch arg {
-			case "-u":
-				fgl = fglUserCurrent
-			case "-r":
-				fgl = fglRuntimeCurrent
-			case "-g":
-				fgl = fglGo
-			case "-s":
-				fgl = fglStart
-			case "-t":
-				flags |= printGoroutinesStack
-			case "-l":
-				flags |= printGoroutinesLabels
-			case "":
-				// nothing to do
-			default:
-				return fmt.Errorf("wrong argument: '%s'", arg)
+		case "-w", "-with":
+			filter, err := readGoroutinesFilter(args, &i)
+			if err != nil {
+				return err
 			}
+			filters = append(filters, *filter)
+
+		case "-wo", "-without":
+			filter, err := readGoroutinesFilter(args, &i)
+			if err != nil {
+				return err
+			}
+			filter.Negated = true
+			filters = append(filters, *filter)
+
+		case "-group":
+			var err error
+			group.GroupBy, err = readGoroutinesFilterKind(args, i+1)
+			if err != nil {
+				return err
+			}
+			i++
+			if group.GroupBy == api.GoroutineLabel {
+				if i+1 >= len(args) {
+					return errors.New("-group label must be followed by an argument")
+				}
+				group.GroupByKey = args[i+1]
+				i++
+			}
+			batchSize = 0 // grouping only works well if run on all goroutines
+
+		case "":
+			// nothing to do
+		default:
+			return fmt.Errorf("wrong argument: '%s'", arg)
 		}
-	default:
-		return fmt.Errorf("too many arguments")
 	}
+
 	state, err := t.client.GetState()
 	if err != nil {
 		return err
 	}
 	var (
-		start = 0
-		gslen = 0
-		gs    []*api.Goroutine
+		start         = 0
+		gslen         = 0
+		gs            []*api.Goroutine
+		groups        []api.GoroutineGroup
+		tooManyGroups bool
 	)
 	t.longCommandStart()
 	for start >= 0 {
@@ -785,21 +878,86 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
 			fmt.Printf("interrupted\n")
 			return nil
 		}
-		gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize)
+		gs, groups, start, tooManyGroups, err = t.client.ListGoroutinesWithFilter(start, batchSize, filters, &group)
 		if err != nil {
 			return err
 		}
-		sort.Sort(byGoroutineID(gs))
-		err = printGoroutines(t, gs, fgl, flags, state)
-		if err != nil {
-			return err
+		if len(groups) > 0 {
+			for i := range groups {
+				fmt.Printf("%s\n", groups[i].Name)
+				err = printGoroutines(t, "\t", gs[groups[i].Offset:][:groups[i].Count], fgl, flags, depth, state)
+				if err != nil {
+					return err
+				}
+				fmt.Printf("\tTotal: %d\n", groups[i].Total)
+				if i != len(groups)-1 {
+					fmt.Printf("\n")
+				}
+			}
+			if tooManyGroups {
+				fmt.Printf("Too many groups\n")
+			}
+		} else {
+			sort.Sort(byGoroutineID(gs))
+			err = printGoroutines(t, "", gs, fgl, flags, depth, state)
+			if err != nil {
+				return err
+			}
+			gslen += len(gs)
 		}
-		gslen += len(gs)
 	}
-	fmt.Printf("[%d goroutines]\n", gslen)
+	if gslen > 0 {
+		fmt.Printf("[%d goroutines]\n", gslen)
+	}
 	return nil
 }
 
+func readGoroutinesFilterKind(args []string, i int) (api.GoroutineField, error) {
+	if i >= len(args) {
+		return api.GoroutineFieldNone, fmt.Errorf("%s must be followed by an argument", args[i-1])
+	}
+
+	switch args[i] {
+	case "curloc":
+		return api.GoroutineCurrentLoc, nil
+	case "userloc":
+		return api.GoroutineUserLoc, nil
+	case "goloc":
+		return api.GoroutineGoLoc, nil
+	case "startloc":
+		return api.GoroutineStartLoc, nil
+	case "label":
+		return api.GoroutineLabel, nil
+	case "running":
+		return api.GoroutineRunning, nil
+	case "user":
+		return api.GoroutineUser, nil
+	default:
+		return api.GoroutineFieldNone, fmt.Errorf("unrecognized argument to %s %s", args[i-1], args[i])
+	}
+}
+
+func readGoroutinesFilter(args []string, pi *int) (*api.ListGoroutinesFilter, error) {
+	r := new(api.ListGoroutinesFilter)
+	var err error
+	r.Kind, err = readGoroutinesFilterKind(args, *pi+1)
+	if err != nil {
+		return nil, err
+	}
+	*pi++
+	switch r.Kind {
+	case api.GoroutineRunning, api.GoroutineUser:
+		return r, nil
+	}
+	if *pi+1 >= len(args) {
+		return nil, fmt.Errorf("%s %s needs to be followed by an expression", args[*pi-1], args[*pi])
+	}
+	r.Arg = args[*pi+1]
+	*pi++
+
+	return r, nil
+}
+
 func selectedGID(state *api.DebuggerState) int {
 	if state.SelectedGoroutine == nil {
 		return 0
diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go
index 8f0bb02b..24da56a0 100644
--- a/pkg/terminal/starbind/starlark_mapping.go
+++ b/pkg/terminal/starbind/starlark_mapping.go
@@ -912,6 +912,18 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
 				return starlark.None, decorateError(thread, err)
 			}
 		}
+		if len(args) > 2 && args[2] != starlark.None {
+			err := unmarshalStarlarkValue(args[2], &rpcArgs.Filters, "Filters")
+			if err != nil {
+				return starlark.None, decorateError(thread, err)
+			}
+		}
+		if len(args) > 3 && args[3] != starlark.None {
+			err := unmarshalStarlarkValue(args[3], &rpcArgs.GoroutineGroupingOptions, "GoroutineGroupingOptions")
+			if err != nil {
+				return starlark.None, decorateError(thread, err)
+			}
+		}
 		for _, kv := range kwargs {
 			var err error
 			switch kv[0].(starlark.String) {
@@ -919,6 +931,10 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
 				err = unmarshalStarlarkValue(kv[1], &rpcArgs.Start, "Start")
 			case "Count":
 				err = unmarshalStarlarkValue(kv[1], &rpcArgs.Count, "Count")
+			case "Filters":
+				err = unmarshalStarlarkValue(kv[1], &rpcArgs.Filters, "Filters")
+			case "GoroutineGroupingOptions":
+				err = unmarshalStarlarkValue(kv[1], &rpcArgs.GoroutineGroupingOptions, "GoroutineGroupingOptions")
 			default:
 				err = fmt.Errorf("unknown argument %q", kv[0])
 			}
diff --git a/service/api/types.go b/service/api/types.go
index 0767f457..b24cf83e 100644
--- a/service/api/types.go
+++ b/service/api/types.go
@@ -586,3 +586,41 @@ type DumpState struct {
 
 	Err string
 }
+
+// ListGoroutinesFilter describes a filtering condition for the
+// ListGoroutines API call.
+type ListGoroutinesFilter struct {
+	Kind    GoroutineField
+	Negated bool
+	Arg     string
+}
+
+// GoroutineField allows referring to a field of a goroutine object.
+type GoroutineField uint8
+
+const (
+	GoroutineFieldNone  GoroutineField = iota
+	GoroutineCurrentLoc                // the goroutine's CurrentLoc
+	GoroutineUserLoc                   // the goroutine's UserLoc
+	GoroutineGoLoc                     // the goroutine's GoStatementLoc
+	GoroutineStartLoc                  // the goroutine's StartLoc
+	GoroutineLabel                     // the goroutine's label
+	GoroutineRunning                   // the goroutine is running
+	GoroutineUser                      // the goroutine is a user goroutine
+)
+
+// GoroutineGroup represents a group of goroutines in the return value of
+// the ListGoroutines API call.
+type GoroutineGroup struct {
+	Name   string // name of this group
+	Offset int    // start offset in the list of goroutines of this group
+	Count  int    // number of goroutines that belong to this group in the list of goroutines
+	Total  int    // total number of goroutines that belong to this group
+}
+
+type GoroutineGroupingOptions struct {
+	GroupBy         GoroutineField
+	GroupByKey      string
+	MaxGroupMembers int
+	MaxGroups       int
+}
diff --git a/service/client.go b/service/client.go
index a83831ef..805b9fb4 100644
--- a/service/client.go
+++ b/service/client.go
@@ -114,6 +114,8 @@ type Client interface {
 
 	// ListGoroutines lists all goroutines.
 	ListGoroutines(start, count int) ([]*api.Goroutine, int, error)
+	// ListGoroutinesWithFilter lists goroutines matching the filters
+	ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error)
 
 	// Returns stacktrace
 	Stacktrace(goroutineID int, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error)
diff --git a/service/dap/server.go b/service/dap/server.go
index f7c8cb42..d88b832c 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -29,6 +29,7 @@ import (
 	"sync"
 
 	"github.com/go-delve/delve/pkg/gobuild"
+	"github.com/go-delve/delve/pkg/goversion"
 	"github.com/go-delve/delve/pkg/locspec"
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/go-delve/delve/pkg/proc"
@@ -268,13 +269,6 @@ func (s *Server) Stop() {
 
 	s.mu.Lock()
 	defer s.mu.Unlock()
-	if s.conn != nil {
-		// Unless Stop() was called after serveDAPCodec()
-		// returned, this will result in closed connection error
-		// on next read, breaking out of the read loop and
-		// allowing the run goroutine to exit.
-		_ = s.conn.Close()
-	}
 
 	if s.debugger != nil {
 		killProcess := s.config.Debugger.AttachPid == 0
@@ -285,7 +279,15 @@ func (s *Server) Stop() {
 	// The binary is no longer in use by the debugger. It is safe to remove it.
 	if s.binaryToRemove != "" {
 		gobuild.Remove(s.binaryToRemove)
-		s.binaryToRemove = ""
+	}
+	// Close client connection last, so other shutdown stages
+	// can send client notifications
+	if s.conn != nil {
+		// Unless Stop() was called after serveDAPCodec()
+		// returned, this will result in closed connection error
+		// on next read, breaking out of the read loop and
+		// allowing the run goroutine to exit.
+		_ = s.conn.Close()
 	}
 	s.log.Debug("DAP server stopped")
 }
@@ -1549,12 +1551,9 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) {
 	}
 
 	// Determine if the goroutine is a system goroutine.
-	// TODO(suzmue): Use the System() method defined in: https://github.com/go-delve/delve/pull/2504
-	g, err := s.debugger.FindGoroutine(goroutineID)
-	var isSystemGoroutine bool
-	if err == nil {
-		userLoc := g.UserCurrent()
-		isSystemGoroutine = fnPackageName(&userLoc) == "runtime"
+	isSystemGoroutine := true
+	if g, _ := s.debugger.FindGoroutine(goroutineID); g != nil {
+		isSystemGoroutine = g.System()
 	}
 
 	stackFrames := make([]dap.StackFrame, len(frames))
@@ -1707,10 +1706,22 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
 		}
 	}
 
-	children, err := s.childrenToDAPVariables(v)
-	if err != nil {
-		s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
-		return
+	var children []dap.Variable
+	if request.Arguments.Filter == "named" || request.Arguments.Filter == "" {
+		named, err := s.metadataToDAPVariables(v)
+		if err != nil {
+			s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
+			return
+		}
+		children = append(children, named...)
+	}
+	if request.Arguments.Filter == "indexed" || request.Arguments.Filter == "" {
+		indexed, err := s.childrenToDAPVariables(v)
+		if err != nil {
+			s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
+			return
+		}
+		children = append(children, indexed...)
 	}
 	response := &dap.VariablesResponse{
 		Response: *newResponse(request.Request),
@@ -1785,6 +1796,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 					Value:              key,
 					VariablesReference: keyref,
 					IndexedVariables:   getIndexedVariableCount(keyv),
+					NamedVariables:     getNamedVariableCount(keyv),
 				}
 				valvar := dap.Variable{
 					Name:               fmt.Sprintf("[val %d]", v.startIndex+kvIndex),
@@ -1793,6 +1805,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 					Value:              val,
 					VariablesReference: valref,
 					IndexedVariables:   getIndexedVariableCount(valv),
+					NamedVariables:     getNamedVariableCount(valv),
 				}
 				children = append(children, keyvar, valvar)
 			} else { // At least one is a scalar
@@ -1813,9 +1826,11 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 					}
 					kvvar.VariablesReference = keyref
 					kvvar.IndexedVariables = getIndexedVariableCount(keyv)
+					kvvar.NamedVariables = getNamedVariableCount(keyv)
 				} else if valref != 0 { // val is a type to be expanded
 					kvvar.VariablesReference = valref
 					kvvar.IndexedVariables = getIndexedVariableCount(valv)
+					kvvar.NamedVariables = getNamedVariableCount(valv)
 				}
 				children = append(children, kvvar)
 			}
@@ -1833,6 +1848,7 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 				Value:              cvalue,
 				VariablesReference: cvarref,
 				IndexedVariables:   getIndexedVariableCount(&v.Children[i]),
+				NamedVariables:     getNamedVariableCount(&v.Children[i]),
 			}
 		}
 	default:
@@ -1872,12 +1888,59 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 				Value:              cvalue,
 				VariablesReference: cvarref,
 				IndexedVariables:   getIndexedVariableCount(c),
+				NamedVariables:     getNamedVariableCount(c),
 			}
 		}
 	}
 	return children, nil
 }
 
+func getNamedVariableCount(v *proc.Variable) int {
+	namedVars := 0
+	if isListOfBytesOrRunes(v) {
+		// string value of array/slice of bytes and runes.
+		namedVars += 1
+	}
+	return namedVars
+}
+
+// metadataToDAPVariables returns the DAP presentation of the referenced variable's metadata.
+// These are included as named variables
+func (s *Server) metadataToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) {
+	var children []dap.Variable
+
+	if isListOfBytesOrRunes(v.Variable) {
+		// Return the string value of []byte or []rune.
+		typeName := api.PrettyTypeName(v.DwarfType)
+		loadExpr := fmt.Sprintf("string(*(*%q)(%#x))", typeName, v.Addr)
+
+		s.log.Debugf("loading %s (type %s) with %s", v.fullyQualifiedNameOrExpr, typeName, loadExpr)
+		// We know that this is an array/slice of Uint8 or Int32, so we will load up to MaxStringLen.
+		config := DefaultLoadConfig
+		config.MaxArrayValues = config.MaxStringLen
+		vLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, loadExpr, config)
+		val := s.convertVariableToString(vLoaded)
+		if err == nil {
+			// TODO(suzmue): Add evaluate name. Using string(name) will not get the same result because the
+			// MaxArrayValues is not auto adjusted in evaluate requests like MaxStringLen is adjusted.
+			children = append(children, dap.Variable{
+				Name:  "string()",
+				Value: val,
+				Type:  "string",
+			})
+		}
+	}
+	return children, nil
+}
+
+func isListOfBytesOrRunes(v *proc.Variable) bool {
+	if len(v.Children) > 0 && (v.Kind == reflect.Array || v.Kind == reflect.Slice) {
+		childKind := v.Children[0].RealType.Common().ReflectKind
+		return childKind == reflect.Uint8 || childKind == reflect.Int32
+	}
+	return false
+}
+
 func (s *Server) getTypeIfSupported(v *proc.Variable) string {
 	if !s.clientCapabilities.supportsVariableType {
 		return ""
@@ -2147,7 +2210,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			opts |= showFullValue
 		}
 		exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts)
-		response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar)}
+		response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)}
 	}
 	s.send(response)
 }
@@ -2445,14 +2508,36 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
 	if bpState != nil && bpState.Breakpoint != nil && (bpState.Breakpoint.Name == proc.FatalThrow || bpState.Breakpoint.Name == proc.UnrecoveredPanic) {
 		switch bpState.Breakpoint.Name {
 		case proc.FatalThrow:
-			// TODO(suzmue): add the fatal throw reason to body.Description.
 			body.ExceptionId = "fatal error"
+			// Attempt to get the value of the throw reason.
+			// This is not currently working for Go 1.16 or 1.17: https://github.com/golang/go/issues/46425.
+			handleError := func(err error) {
+				if err != nil {
+					body.Description = fmt.Sprintf("Error getting throw reason: %s", err.Error())
+				}
+				if goversion.ProducerAfterOrEqual(s.debugger.TargetGoVersion(), 1, 16) {
+					body.Description = "Throw reason unavailable, see https://github.com/golang/go/issues/46425"
+				}
+			}
+
+			exprVar, err := s.debugger.EvalVariableInScope(goroutineID, 1, 0, "s", DefaultLoadConfig)
+			if err == nil {
+				if exprVar.Value != nil {
+					body.Description = exprVar.Value.String()
+				} else {
+					handleError(exprVar.Unreadable)
+				}
+			} else {
+				handleError(err)
+			}
 		case proc.UnrecoveredPanic:
 			body.ExceptionId = "panic"
 			// Attempt to get the value of the panic message.
 			exprVar, err := s.debugger.EvalVariableInScope(goroutineID, 0, 0, "(*msgs).arg.(data)", DefaultLoadConfig)
 			if err == nil {
 				body.Description = exprVar.Value.String()
+			} else {
+				body.Description = fmt.Sprintf("Error getting panic message: %s", err.Error())
 			}
 		}
 	} else {
@@ -2481,7 +2566,7 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
 	}
 
 	frames, err := s.debugger.Stacktrace(goroutineID, s.args.stackTraceDepth, 0)
-	if err == nil && len(frames) > 0 {
+	if err == nil {
 		apiFrames, err := s.debugger.ConvertStacktrace(frames, nil)
 		if err == nil {
 			var buf bytes.Buffer
@@ -2498,6 +2583,8 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
 			})
 			body.Details.StackTrace = buf.String()
 		}
+	} else {
+		body.Details.StackTrace = fmt.Sprintf("Error getting stack trace: %s", err.Error())
 	}
 	response := &dap.ExceptionInfoResponse{
 		Response: *newResponse(request.Request),
@@ -2641,6 +2728,7 @@ func (s *Server) doRunCommand(command string, asyncSetupDone chan struct{}) {
 			if strings.HasPrefix(state.CurrentThread.Breakpoint.Name, functionBpPrefix) {
 				stopped.Body.Reason = "function breakpoint"
 			}
+			stopped.Body.HitBreakpointIds = []int{state.CurrentThread.Breakpoint.ID}
 		}
 	} else {
 		s.exceptionErr = err
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 6b3d29c2..14ce3e93 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -237,6 +237,12 @@ func (d *Debugger) checkGoVersion() error {
 	return goversion.Compatible(producer)
 }
 
+func (d *Debugger) TargetGoVersion() string {
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+	return d.target.BinInfo().Producer()
+}
+
 // Launch will start a process with the given args and working directory.
 func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) {
 	if err := verifyBinaryFormat(processArgs[0]); err != nil {
@@ -620,7 +626,33 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
 	return state, nil
 }
 
-// CreateBreakpoint creates a breakpoint.
+// CreateBreakpoint creates a breakpoint using information from the provided `requestedBp`.
+// This function accepts several different ways of specifying where and how to create the
+// breakpoint that has been requested. Any error encountered during the attempt to set the
+// breakpoint will be returned to the caller.
+//
+// The ways of specifying a breakpoint are listed below in the order they are considered by
+// this function:
+//
+// - If requestedBp.TraceReturn is true then it is expected that
+// requestedBp.Addrs will contain the list of return addresses
+// supplied by the caller.
+//
+// - If requestedBp.File is not an empty string the breakpoint
+// will be created on the specified file:line location
+//
+// - If requestedBp.FunctionName is not an empty string
+// the breakpoint will be created on the specified function:line
+// location.
+//
+// - If requestedBp.Addrs is filled it will create a logical breakpoint
+// corresponding to all specified addresses.
+//
+// - Otherwise the value specified by arg.Breakpoint.Addr will be used.
+//
+// Note that this method will use the first successful method in order to
+// create a breakpoint, so mixing different fields will not result is multiple
+// breakpoints being set.
 func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
@@ -1261,7 +1293,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
 			continue
 		}
 
-		s, err := proc.GoroutineScope(thread)
+		s, err := proc.GoroutineScope(d.target, thread)
 		if err != nil {
 			return err
 		}
@@ -1366,7 +1398,7 @@ func (d *Debugger) PackageVariables(filter string, cfg proc.LoadConfig) ([]*proc
 		return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
 	}
 
-	scope, err := proc.ThreadScope(d.target.CurrentThread())
+	scope, err := proc.ThreadScope(d.target, d.target.CurrentThread())
 	if err != nil {
 		return nil, err
 	}
@@ -1493,6 +1525,131 @@ func (d *Debugger) Goroutines(start, count int) ([]*proc.G, int, error) {
 	return proc.GoroutinesInfo(d.target, start, count)
 }
 
+// FilterGoroutines returns the goroutines in gs that satisfy the specified filters.
+func (d *Debugger) FilterGoroutines(gs []*proc.G, filters []api.ListGoroutinesFilter) []*proc.G {
+	if len(filters) == 0 {
+		return gs
+	}
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+	r := []*proc.G{}
+	for _, g := range gs {
+		ok := true
+		for i := range filters {
+			if !matchGoroutineFilter(g, &filters[i]) {
+				ok = false
+				break
+			}
+		}
+		if ok {
+			r = append(r, g)
+		}
+	}
+	return r
+}
+
+func matchGoroutineFilter(g *proc.G, filter *api.ListGoroutinesFilter) bool {
+	var val bool
+	switch filter.Kind {
+	default:
+		fallthrough
+	case api.GoroutineFieldNone:
+		val = true
+	case api.GoroutineCurrentLoc:
+		val = matchGoroutineLocFilter(g.CurrentLoc, filter.Arg)
+	case api.GoroutineUserLoc:
+		val = matchGoroutineLocFilter(g.UserCurrent(), filter.Arg)
+	case api.GoroutineGoLoc:
+		val = matchGoroutineLocFilter(g.Go(), filter.Arg)
+	case api.GoroutineStartLoc:
+		val = matchGoroutineLocFilter(g.StartLoc(), filter.Arg)
+	case api.GoroutineLabel:
+		idx := strings.Index(filter.Arg, "=")
+		if idx >= 0 {
+			val = g.Labels()[filter.Arg[:idx]] == filter.Arg[idx+1:]
+		} else {
+			_, val = g.Labels()[filter.Arg]
+		}
+	case api.GoroutineRunning:
+		val = g.Thread != nil
+	case api.GoroutineUser:
+		val = !g.System()
+	}
+	if filter.Negated {
+		val = !val
+	}
+	return val
+}
+
+func matchGoroutineLocFilter(loc proc.Location, arg string) bool {
+	return strings.Contains(formatLoc(loc), arg)
+}
+
+func formatLoc(loc proc.Location) string {
+	fnname := "?"
+	if loc.Fn != nil {
+		fnname = loc.Fn.Name
+	}
+	return fmt.Sprintf("%s:%d in %s", loc.File, loc.Line, fnname)
+}
+
+// GroupGoroutines divides goroutines in gs into groups as specified by groupBy and groupByArg.
+// A maximum of maxGoroutinesPerGroup are saved in each group, but the total
+// number of goroutines in each group is recorded.
+func (d *Debugger) GroupGoroutines(gs []*proc.G, group *api.GoroutineGroupingOptions) ([]*proc.G, []api.GoroutineGroup, bool) {
+	if group.GroupBy == api.GoroutineFieldNone {
+		return gs, nil, false
+	}
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+
+	groupMembers := map[string][]*proc.G{}
+	totals := map[string]int{}
+
+	for _, g := range gs {
+		var key string
+		switch group.GroupBy {
+		case api.GoroutineCurrentLoc:
+			key = formatLoc(g.CurrentLoc)
+		case api.GoroutineUserLoc:
+			key = formatLoc(g.UserCurrent())
+		case api.GoroutineGoLoc:
+			key = formatLoc(g.Go())
+		case api.GoroutineStartLoc:
+			key = formatLoc(g.StartLoc())
+		case api.GoroutineLabel:
+			key = fmt.Sprintf("%s=%s", group.GroupByKey, g.Labels()[group.GroupByKey])
+		case api.GoroutineRunning:
+			key = fmt.Sprintf("running=%v", g.Thread != nil)
+		case api.GoroutineUser:
+			key = fmt.Sprintf("user=%v", !g.System())
+		}
+		if len(groupMembers[key]) < group.MaxGroupMembers {
+			groupMembers[key] = append(groupMembers[key], g)
+		}
+		totals[key]++
+	}
+
+	keys := make([]string, 0, len(groupMembers))
+	for key := range groupMembers {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+
+	tooManyGroups := false
+	gsout := []*proc.G{}
+	groups := []api.GoroutineGroup{}
+	for _, key := range keys {
+		if group.MaxGroups > 0 && len(groups) >= group.MaxGroups {
+			tooManyGroups = true
+			break
+		}
+		groups = append(groups, api.GoroutineGroup{Name: key, Offset: len(gsout), Count: len(groupMembers[key]), Total: totals[key]})
+		gsout = append(gsout, groupMembers[key]...)
+	}
+	return gsout, groups, tooManyGroups
+}
+
 // Stacktrace returns a list of Stackframes for the given goroutine. The
 // length of the returned list will be min(stack_len, depth).
 // If 'full' is true, then local vars, function args, etc will be returned as well.
@@ -1585,7 +1742,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.Memory(), nil, rawlocs[i:]...)
+			scope := proc.FrameToScope(d.target, d.target.BinInfo(), d.target.Memory(), nil, rawlocs[i:]...)
 			locals, err := scope.LocalVariables(*cfg)
 			if err != nil {
 				return nil, err
diff --git a/service/rpc2/client.go b/service/rpc2/client.go
index 314274fd..21bf88f6 100644
--- a/service/rpc2/client.go
+++ b/service/rpc2/client.go
@@ -226,6 +226,10 @@ func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) {
 	return &out.Breakpoint, err
 }
 
+// CreateBreakpoint will send a request to the RPC server to create a breakpoint.
+// Please refer to the documentation for `Debugger.CreateBreakpoint` for a description of how
+// the requested breakpoint parameters are interpreted and used:
+// https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint
 func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) {
 	var out CreateBreakpointOut
 	err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint}, &out)
@@ -352,10 +356,19 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([
 
 func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) {
 	var out ListGoroutinesOut
-	err := c.call("ListGoroutines", ListGoroutinesIn{start, count}, &out)
+	err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}}, &out)
 	return out.Goroutines, out.Nextg, err
 }
 
+func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) {
+	if group == nil {
+		group = &api.GoroutineGroupingOptions{}
+	}
+	var out ListGoroutinesOut
+	err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group}, &out)
+	return out.Goroutines, out.Groups, out.Nextg, out.TooManyGroups, err
+}
+
 func (c *RPCClient) Stacktrace(goroutineId, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error) {
 	var out StacktraceOut
 	err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, false, opts, cfg}, &out)
diff --git a/service/rpc2/server.go b/service/rpc2/server.go
index 05dbfbda..2a0607aa 100644
--- a/service/rpc2/server.go
+++ b/service/rpc2/server.go
@@ -235,19 +235,10 @@ type CreateBreakpointOut struct {
 	Breakpoint api.Breakpoint
 }
 
-// CreateBreakpoint creates a new breakpoint.
-//
-// - If arg.Breakpoint.File is not an empty string the breakpoint
-// will be created on the specified file:line location
-//
-// - If arg.Breakpoint.FunctionName is not an empty string
-// the breakpoint will be created on the specified function:line
-// location.
-//
-// - If arg.Breakpoint.Addrs is filled it will create a logical breakpoint
-// corresponding to all specified addresses.
-//
-// - Otherwise the value specified by arg.Breakpoint.Addr will be used.
+// CreateBreakpoint creates a new breakpoint. The client is expected to populate `CreateBreakpointIn`
+// with an `api.Breakpoint` struct describing where to set the breakpoing. For more information on
+// how to properly request a breakpoint via the `api.Breakpoint` struct see the documentation for
+// `debugger.CreateBreakpoint` here: https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint.
 func (s *RPCServer) CreateBreakpoint(arg CreateBreakpointIn, out *CreateBreakpointOut) error {
 	if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil {
 		return err
@@ -594,11 +585,16 @@ func (s *RPCServer) ListTypes(arg ListTypesIn, out *ListTypesOut) error {
 type ListGoroutinesIn struct {
 	Start int
 	Count int
+
+	Filters []api.ListGoroutinesFilter
+	api.GoroutineGroupingOptions
 }
 
 type ListGoroutinesOut struct {
-	Goroutines []*api.Goroutine
-	Nextg      int
+	Goroutines    []*api.Goroutine
+	Nextg         int
+	Groups        []api.GoroutineGroup
+	TooManyGroups bool
 }
 
 // ListGoroutines lists all goroutines.
@@ -607,11 +603,37 @@ type ListGoroutinesOut struct {
 // parameter, to get more goroutines from ListGoroutines.
 // Passing a value of Start that wasn't returned by ListGoroutines will skip
 // an undefined number of goroutines.
+//
+// If arg.Filters are specified the list of returned goroutines is filtered
+// applying the specified filters.
+// For example:
+//    ListGoroutinesFilter{ Kind: ListGoroutinesFilterUserLoc, Negated: false, Arg: "afile.go" }
+// will only return goroutines whose UserLoc contains "afile.go" as a substring.
+// More specifically a goroutine matches a location filter if the specified
+// location, formatted like this:
+//    filename:lineno in function
+// contains Arg[0] as a substring.
+//
+// Filters can also be applied to goroutine labels:
+//    ListGoroutineFilter{ Kind: ListGoroutinesFilterLabel, Negated: false, Arg: "key=value" }
+// this filter will only return goroutines that have a key=value label.
+//
+// If arg.GroupBy is not GoroutineFieldNone then the goroutines will
+// be grouped with the specified criterion.
+// If the value of arg.GroupBy is GoroutineLabel goroutines will
+// be grouped by the value of the label with key GroupByKey.
+// For each group a maximum of MaxExamples example goroutines are
+// returned, as well as the total number of goroutines in the group.
 func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut) error {
+	//TODO(aarzilli): if arg contains a running goroutines filter (not negated)
+	// and start == 0 and count == 0 then we can optimize this by just looking
+	// at threads directly.
 	gs, nextg, err := s.debugger.Goroutines(arg.Start, arg.Count)
 	if err != nil {
 		return err
 	}
+	gs = s.debugger.FilterGoroutines(gs, arg.Filters)
+	gs, out.Groups, out.TooManyGroups = s.debugger.GroupGoroutines(gs, &arg.GoroutineGroupingOptions)
 	s.debugger.LockTarget()
 	defer s.debugger.UnlockTarget()
 	out.Goroutines = api.ConvertGoroutines(gs)


Test patch:
diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md
index 766c18b8..152fb6b3 100644
--- a/Documentation/backend_test_health.md
+++ b/Documentation/backend_test_health.md
@@ -21,8 +21,8 @@ Tests skipped by each supported backend:
 	* 1 broken
 * linux/arm64 skipped = 1
 	* 1 broken - cgo stacktraces
-* pie skipped = 1
-	* 1 upstream issue - https://github.com/golang/go/issues/29322
+* pie skipped = 2
+	* 2 upstream issue - https://github.com/golang/go/issues/29322
 * rr skipped = 2
 	* 2 not implemented
 * windows skipped = 4
diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go
index 00a082f5..2c5b23a7 100644
--- a/cmd/dlv/dlv_test.go
+++ b/cmd/dlv/dlv_test.go
@@ -326,6 +326,8 @@ func TestRedirect(t *testing.T) {
 	cmd.Wait()
 }
 
+const checkAutogenDocLongOutput = false
+
 func checkAutogenDoc(t *testing.T, filename, gencommand string, generated []byte) {
 	saved := slurpFile(t, filepath.Join(projectRoot(), filename))
 
@@ -333,13 +335,17 @@ func checkAutogenDoc(t *testing.T, filename, gencommand string, generated []byte
 	generated = bytes.ReplaceAll(generated, []byte("\r\n"), []byte{'\n'})
 
 	if len(saved) != len(generated) {
-		t.Logf("generated %q saved %q\n", generated, saved)
+		if checkAutogenDocLongOutput {
+			t.Logf("generated %q saved %q\n", generated, saved)
+		}
 		t.Fatalf("%s: needs to be regenerated; run %s", filename, gencommand)
 	}
 
 	for i := range saved {
 		if saved[i] != generated[i] {
-			t.Logf("generated %q saved %q\n", generated, saved)
+			if checkAutogenDocLongOutput {
+				t.Logf("generated %q saved %q\n", generated, saved)
+			}
 			t.Fatalf("%s: needs to be regenerated; run %s", filename, gencommand)
 		}
 	}
diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go
index 7c218ed8..cdd0f282 100644
--- a/pkg/proc/core/core_test.go
+++ b/pkg/proc/core/core_test.go
@@ -297,7 +297,7 @@ func TestCore(t *testing.T) {
 	if mainFrame == nil {
 		t.Fatalf("Couldn't find main in stack %v", panickingStack)
 	}
-	msg, err := proc.FrameToScope(p.BinInfo(), p.Memory(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64})
+	msg, err := proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64})
 	if err != nil {
 		t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err)
 	}
@@ -427,7 +427,7 @@ mainSearch:
 		t.Fatal("could not find main.main frame")
 	}
 
-	scope := proc.FrameToScope(p.BinInfo(), p.Memory(), nil, *mainFrame)
+	scope := proc.FrameToScope(p, 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/dwarf_export_test.go b/pkg/proc/dwarf_export_test.go
index b28c8bba..b6d22c08 100644
--- a/pkg/proc/dwarf_export_test.go
+++ b/pkg/proc/dwarf_export_test.go
@@ -7,7 +7,7 @@ func (bi *BinaryInfo) PackageVars() []packageVar {
 	return bi.packageVars
 }
 
-func NewCompositeMemory(p *Target, pieces []op.Piece) (*compositeMemory, error) {
+func NewCompositeMemory(p *Target, pieces []op.Piece, base uint64) (*compositeMemory, error) {
 	regs, err := p.CurrentThread().Registers()
 	if err != nil {
 		return nil, err
@@ -17,5 +17,9 @@ func NewCompositeMemory(p *Target, pieces []op.Piece) (*compositeMemory, error)
 	dwarfregs := arch.RegistersToDwarfRegisters(0, regs)
 	dwarfregs.ChangeFunc = p.CurrentThread().SetReg
 
-	return newCompositeMemory(p.Memory(), arch, *dwarfregs, pieces)
+	mem, err := newCompositeMemory(p.Memory(), arch, *dwarfregs, pieces)
+	if mem != nil {
+		mem.base = base
+	}
+	return mem, err
 }
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index a455a445..caeeab5a 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -1149,10 +1149,10 @@ 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.Memory(), nil, frame)
+			scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame)
 		}
 	} else {
-		scope, err = proc.GoroutineScope(p.CurrentThread())
+		scope, err = proc.GoroutineScope(p, p.CurrentThread())
 	}
 
 	if err != nil {
@@ -1172,7 +1172,7 @@ func evalVariable(p *proc.Target, t testing.TB, symbol string) *proc.Variable {
 }
 
 func setVariable(p *proc.Target, symbol, value string) error {
-	scope, err := proc.GoroutineScope(p.CurrentThread())
+	scope, err := proc.GoroutineScope(p, p.CurrentThread())
 	if err != nil {
 		return err
 	}
@@ -1335,6 +1335,36 @@ func TestFrameEvaluation(t *testing.T) {
 	})
 }
 
+func TestThreadFrameEvaluation(t *testing.T) {
+	skipOn(t, "upstream issue - https://github.com/golang/go/issues/29322", "pie")
+	deadlockBp := proc.FatalThrow
+	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
+		t.SkipNow()
+	}
+	withTestProcess("testdeadlock", t, func(p *proc.Target, fixture protest.Fixture) {
+		assertNoError(p.Continue(), t, "Continue()")
+
+		bp := p.CurrentThread().Breakpoint()
+		if bp.Breakpoint == nil || bp.Name != deadlockBp {
+			t.Fatalf("did not stop at deadlock breakpoint %v", bp)
+		}
+
+		// There is no selected goroutine during a deadlock, so the scope will
+		// be a thread scope.
+		scope, err := proc.ConvertEvalScope(p, 0, 0, 0)
+		assertNoError(err, t, "ConvertEvalScope() on frame 0")
+		_, err = scope.EvalVariable("s", normalLoadConfig)
+		if err == nil {
+			t.Errorf("expected error for EvalVariable(\"s\") on frame 0")
+		}
+
+		scope, err = proc.ConvertEvalScope(p, 0, 1, 0)
+		assertNoError(err, t, "ConvertEvalScope() on frame 1")
+		_, err = scope.EvalVariable("s", normalLoadConfig)
+		assertNoError(err, t, "EvalVariable(\"s\") on frame 1")
+	})
+}
+
 func TestPointerSetting(t *testing.T) {
 	withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), t, "Continue() returned an error")
@@ -1350,7 +1380,7 @@ func TestPointerSetting(t *testing.T) {
 		pval(1)
 
 		// change p1 to point to i2
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "Scope()")
 		i2addr, err := scope.EvalExpression("i2", normalLoadConfig)
 		assertNoError(err, t, "EvalExpression()")
@@ -1481,7 +1511,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
 				if bp := th.Breakpoint(); bp.Breakpoint == nil {
 					continue
 				}
-				scope, err := proc.GoroutineScope(th)
+				scope, err := proc.GoroutineScope(p, th)
 				assertNoError(err, t, "Scope()")
 				v, err := scope.EvalVariable("i", normalLoadConfig)
 				assertNoError(err, t, "evalVariable")
@@ -1551,7 +1581,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) {
 		assertNoError(p.Continue(), b, "Continue()")
 		b.ResetTimer()
 		for i := 0; i < b.N; i++ {
-			p.ClearAllGCache()
+			p.ClearCaches()
 			_, _, err := proc.GoroutinesInfo(p, 0, 0)
 			assertNoError(err, b, "GoroutinesInfo")
 		}
@@ -1611,7 +1641,7 @@ func TestPointerLoops(t *testing.T) {
 func BenchmarkLocalVariables(b *testing.B) {
 	withTestProcess("testvariables", b, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), b, "Continue() returned an error")
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, b, "Scope()")
 		b.ResetTimer()
 		for i := 0; i < b.N; i++ {
@@ -1900,7 +1930,7 @@ func TestPackageVariables(t *testing.T) {
 	withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
 		err := p.Continue()
 		assertNoError(err, t, "Continue()")
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "Scope()")
 		vars, err := scope.PackageVariables(normalLoadConfig)
 		assertNoError(err, t, "PackageVariables()")
@@ -2698,7 +2728,7 @@ func BenchmarkTrace(b *testing.B) {
 		b.ResetTimer()
 		for i := 0; i < b.N; i++ {
 			assertNoError(p.Continue(), b, "Continue()")
-			s, err := proc.GoroutineScope(p.CurrentThread())
+			s, err := proc.GoroutineScope(p, p.CurrentThread())
 			assertNoError(err, b, "Scope()")
 			_, err = s.FunctionArguments(proc.LoadConfig{false, 0, 64, 0, 3, 0})
 			assertNoError(err, b, "FunctionArguments()")
@@ -2966,10 +2996,10 @@ func TestIssue871(t *testing.T) {
 			var frame proc.Stackframe
 			frame, err = findFirstNonRuntimeFrame(p)
 			if err == nil {
-				scope = proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame)
+				scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame)
 			}
 		} else {
-			scope, err = proc.GoroutineScope(p.CurrentThread())
+			scope, err = proc.GoroutineScope(p, p.CurrentThread())
 		}
 		assertNoError(err, t, "scope")
 
@@ -3007,7 +3037,7 @@ func TestShadowedFlag(t *testing.T) {
 	}
 	withTestProcess("testshadow", t, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), t, "Continue")
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope")
 		locals, err := scope.LocalVariables(normalLoadConfig)
 		assertNoError(err, t, "LocalVariables")
@@ -3412,7 +3442,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.Memory(), nil, frames[2:]...)
+		scope := proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frames[2:]...)
 		args, _ := scope.FunctionArguments(normalLoadConfig)
 		assertNoError(err, t, "FunctionArguments()")
 		if len(args) > 0 {
@@ -3446,7 +3476,7 @@ func testDeclLineCount(t *testing.T, p *proc.Target, lineno int, tgtvars []strin
 	sort.Strings(tgtvars)
 
 	assertLineNumber(p, t, lineno, "Program did not continue to correct next location")
-	scope, err := proc.GoroutineScope(p.CurrentThread())
+	scope, err := proc.GoroutineScope(p, p.CurrentThread())
 	assertNoError(err, t, fmt.Sprintf("GoroutineScope (:%d)", lineno))
 	vars, err := scope.Locals()
 	assertNoError(err, t, fmt.Sprintf("Locals (:%d)", lineno))
@@ -3864,7 +3894,7 @@ func TestIssue951(t *testing.T) {
 
 	withTestProcess("issue951", t, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), t, "Continue()")
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope")
 		args, err := scope.FunctionArguments(normalLoadConfig)
 		assertNoError(err, t, "FunctionArguments")
@@ -3910,7 +3940,7 @@ func TestMapLoadConfigWithReslice(t *testing.T) {
 	withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
 		zolotovLoadCfg := proc.LoadConfig{FollowPointers: true, MaxStructFields: -1, MaxVariableRecurse: 3, MaxStringLen: 10, MaxArrayValues: 10}
 		assertNoError(p.Continue(), t, "First Continue()")
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope")
 		m1, err := scope.EvalExpression("m1", zolotovLoadCfg)
 		assertNoError(err, t, "EvalVariable")
@@ -4152,7 +4182,7 @@ func TestIssue1432(t *testing.T) {
 		svar := evalVariable(p, t, "s")
 		t.Logf("%#x", svar.Addr)
 
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope()")
 
 		err = scope.SetVariable(fmt.Sprintf("(*\"main.s\")(%#x).i", svar.Addr), "10")
@@ -5054,8 +5084,8 @@ func TestDump(t *testing.T) {
 					t.Errorf("Frame mismatch %d.%d\nlive:\t%s\ncore:\t%s", gos[i].ID, j, convertFrame(p.BinInfo().Arch, &frames[j]), convertFrame(p.BinInfo().Arch, &cframes[j]))
 				}
 				if frames[j].Call.Fn != nil && frames[j].Call.Fn.Name == "main.main" {
-					scope = proc.FrameToScope(p.BinInfo(), p.Memory(), gos[i], frames[j:]...)
-					cscope = proc.FrameToScope(c.BinInfo(), c.Memory(), cgos[i], cframes[j:]...)
+					scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), gos[i], frames[j:]...)
+					cscope = proc.FrameToScope(c, c.BinInfo(), c.Memory(), cgos[i], cframes[j:]...)
 				}
 			}
 		}
@@ -5132,9 +5162,11 @@ func TestCompositeMemoryWrite(t *testing.T) {
 			return regs.PC(), rax, xmm1
 		}
 
+		const fakeAddress = 0xbeef0000
+
 		getmem := func(mem proc.MemoryReader) uint64 {
 			buf := make([]byte, 8)
-			_, err := mem.ReadMemory(buf, 0xbeef0000)
+			_, err := mem.ReadMemory(buf, fakeAddress)
 			assertNoError(err, t, "ReadMemory")
 			return binary.LittleEndian.Uint64(buf)
 		}
@@ -5143,9 +5175,9 @@ func TestCompositeMemoryWrite(t *testing.T) {
 		oldPc, oldRax, oldXmm1 := getregs()
 		t.Logf("PC %#x AX %#x XMM1 %#x", oldPc, oldRax, oldXmm1)
 
-		memRax, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 0, Kind: op.RegPiece}})
+		memRax, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 0, Kind: op.RegPiece}}, fakeAddress)
 		assertNoError(err, t, "NewCompositeMemory (rax)")
-		memXmm1, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 18, Kind: op.RegPiece}})
+		memXmm1, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 18, Kind: op.RegPiece}}, fakeAddress)
 		assertNoError(err, t, "NewCompositeMemory (xmm1)")
 
 		if memRax := getmem(memRax); memRax != oldRax {
@@ -5204,7 +5236,7 @@ func TestWatchpointsBasic(t *testing.T) {
 		assertNoError(p.Continue(), t, "Continue 0")
 		assertLineNumber(p, t, 11, "Continue 0") // Position 0
 
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope")
 
 		bp, err := p.SetWatchpoint(scope, "globalvar1", proc.WatchWrite, nil)
@@ -5250,7 +5282,7 @@ func TestWatchpointCounts(t *testing.T) {
 		setFunctionBreakpoint(p, t, "main.main")
 		assertNoError(p.Continue(), t, "Continue 0")
 
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope")
 
 		bp, err := p.SetWatchpoint(scope, "globalvar1", proc.WatchWrite, nil)
diff --git a/pkg/proc/scope_test.go b/pkg/proc/scope_test.go
index 6435fb72..df371338 100644
--- a/pkg/proc/scope_test.go
+++ b/pkg/proc/scope_test.go
@@ -238,7 +238,7 @@ func (check *scopeCheck) Parse(descr string, t *testing.T) {
 }
 
 func (scopeCheck *scopeCheck) checkLocalsAndArgs(p *proc.Target, t *testing.T) (*proc.EvalScope, bool) {
-	scope, err := proc.GoroutineScope(p.CurrentThread())
+	scope, err := proc.GoroutineScope(p, p.CurrentThread())
 	assertNoError(err, t, "GoroutineScope()")
 
 	ok := true
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index f62a7136..3ad61ae1 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -353,6 +353,14 @@ func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) {
 	c.send(request)
 }
 
+// NamedVariablesRequest sends a 'variables' request.
+func (c *Client) NamedVariablesRequest(variablesReference int) {
+	request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
+	request.Arguments.VariablesReference = variablesReference
+	request.Arguments.Filter = "named"
+	c.send(request)
+}
+
 // TeriminateRequest sends a 'terminate' request.
 func (c *Client) TerminateRequest() {
 	c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 0e042a87..e7a0c31f 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -1524,7 +1524,6 @@ func TestVariablesLoading(t *testing.T) {
 						longarr = client.ExpectVariablesResponse(t)
 						checkChildren(t, longarr, "longarr", 50)
 						checkArrayChildren(t, longarr, "longarr", 50)
-
 					}
 
 					// Slice not fully loaded based on LoadConfig.MaxArrayValues.
@@ -1789,6 +1788,89 @@ func TestVariablesLoading(t *testing.T) {
 	})
 }
 
+// TestVariablesMetadata exposes test cases where variables contain metadata that
+// can be accessed by requesting named variables.
+func TestVariablesMetadata(t *testing.T) {
+	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Breakpoints are set within the program
+			fixture.Source, []int{},
+			[]onBreakpoint{{
+				execute:    func() {},
+				disconnect: false,
+			}, {
+				execute: func() {
+					checkStop(t, client, 1, "main.main", 368)
+
+					client.VariablesRequest(1001) // Locals
+					locals := client.ExpectVariablesResponse(t)
+
+					checkNamedChildren := func(ref int, name, typeStr string, vals []string, evaluate bool) {
+						// byteslice, request named variables
+						client.NamedVariablesRequest(ref)
+						named := client.ExpectVariablesResponse(t)
+						checkChildren(t, named, name, 1)
+						checkVarExact(t, named, 0, "string()", "", "\"tèst\"", "string", false)
+
+						client.VariablesRequest(ref)
+						all := client.ExpectVariablesResponse(t)
+						checkChildren(t, all, name, len(vals)+1)
+						checkVarExact(t, all, 0, "string()", "", "\"tèst\"", "string", false)
+						for i, v := range vals {
+							idx := fmt.Sprintf("[%d]", i)
+							evalName := fmt.Sprintf("%s[%d]", name, i)
+							if evaluate {
+								evalName = fmt.Sprintf("(%s)[%d]", name, i)
+							}
+							checkVarExact(t, all, i+1, idx, evalName, v, typeStr, false)
+						}
+					}
+
+					// byteslice
+					ref := checkVarExactIndexed(t, locals, -1, "byteslice", "byteslice", "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", "[]uint8", true, 5, 1)
+					checkNamedChildren(ref, "byteslice", "uint8", []string{"116", "195", "168", "115", "116"}, false)
+
+					client.EvaluateRequest("byteslice", 0, "")
+					got := client.ExpectEvaluateResponse(t)
+					ref = checkEvalIndexed(t, got, "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", hasChildren, 5, 1)
+					checkNamedChildren(ref, "byteslice", "uint8", []string{"116", "195", "168", "115", "116"}, true)
+
+					// runeslice
+					ref = checkVarExactIndexed(t, locals, -1, "runeslice", "runeslice", "[]int32 len: 4, cap: 4, [116,232,115,116]", "[]int32", true, 4, 1)
+					checkNamedChildren(ref, "runeslice", "int32", []string{"116", "232", "115", "116"}, false)
+
+					client.EvaluateRequest("runeslice", 0, "repl")
+					got = client.ExpectEvaluateResponse(t)
+					ref = checkEvalIndexed(t, got, "[]int32 len: 4, cap: 4, [116,232,115,116]", hasChildren, 4, 1)
+					checkNamedChildren(ref, "runeslice", "int32", []string{"116", "232", "115", "116"}, true)
+
+					// bytearray
+					ref = checkVarExactIndexed(t, locals, -1, "bytearray", "bytearray", "[5]uint8 [116,195,168,115,116]", "[5]uint8", true, 5, 1)
+					checkNamedChildren(ref, "bytearray", "uint8", []string{"116", "195", "168", "115", "116"}, false)
+
+					client.EvaluateRequest("bytearray", 0, "hover")
+					got = client.ExpectEvaluateResponse(t)
+					ref = checkEvalIndexed(t, got, "[5]uint8 [116,195,168,115,116]", hasChildren, 5, 1)
+					checkNamedChildren(ref, "bytearray", "uint8", []string{"116", "195", "168", "115", "116"}, true)
+
+					// runearray
+					ref = checkVarExactIndexed(t, locals, -1, "runearray", "runearray", "[4]int32 [116,232,115,116]", "[4]int32", true, 4, 1)
+					checkNamedChildren(ref, "runearray", "int32", []string{"116", "232", "115", "116"}, false)
+
+					client.EvaluateRequest("runearray", 0, "watch")
+					got = client.ExpectEvaluateResponse(t)
+					ref = checkEvalIndexed(t, got, "[4]int32 [116,232,115,116]", hasChildren, 4, 1)
+					checkNamedChildren(ref, "runearray", "int32", []string{"116", "232", "115", "116"}, true)
+				},
+				disconnect: true,
+			}})
+	})
+}
+
 // TestGlobalScopeAndVariables launches the program with showGlobalVariables
 // arg set, executes to a breakpoint in the main package and tests that global
 // package main variables got loaded. It then steps into a function
@@ -2037,6 +2119,68 @@ func TestSetBreakpoint(t *testing.T) {
 	})
 }
 
+func checkHitBreakpointIds(t *testing.T, se *dap.StoppedEvent, reason string, id int) {
+	if se.Body.ThreadId != 1 || se.Body.Reason != reason || len(se.Body.HitBreakpointIds) != 1 || se.Body.HitBreakpointIds[0] != id {
+		t.Errorf("got %#v, want Reason=%q, ThreadId=1, HitBreakpointIds=[]int{%d}", se, reason, id)
+	}
+}
+
+// TestHitBreakpointIds executes to a breakpoint and tests that
+// the breakpoint ids in the stopped event are correct.
+func TestHitBreakpointIds(t *testing.T) {
+	runTest(t, "locationsprog", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{30}, // b main.main
+			[]onBreakpoint{{
+				execute: func() {
+					checkStop(t, client, 1, "main.main", 30)
+
+					// Set two source breakpoints and two function breakpoints.
+					client.SetBreakpointsRequest(fixture.Source, []int{23, 33})
+					sourceBps := client.ExpectSetBreakpointsResponse(t).Body.Breakpoints
+					checkBreakpoints(t, client, []Breakpoint{{line: 23, path: fixture.Source, verified: true}, {line: 33, path: fixture.Source, verified: true}}, sourceBps)
+
+					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
+						{Name: "anotherFunction"},
+						{Name: "anotherFunction:1"},
+					})
+					functionBps := client.ExpectSetFunctionBreakpointsResponse(t).Body.Breakpoints
+					checkBreakpoints(t, client, []Breakpoint{{line: 26, path: fixture.Source, verified: true}, {line: 27, path: fixture.Source, verified: true}}, functionBps)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se := client.ExpectStoppedEvent(t)
+					checkHitBreakpointIds(t, se, "breakpoint", sourceBps[1].Id)
+					checkStop(t, client, 1, "main.main", 33)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se = client.ExpectStoppedEvent(t)
+					checkHitBreakpointIds(t, se, "breakpoint", sourceBps[0].Id)
+					checkStop(t, client, 1, "main.(*SomeType).SomeFunction", 23)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se = client.ExpectStoppedEvent(t)
+					checkHitBreakpointIds(t, se, "function breakpoint", functionBps[0].Id)
+					checkStop(t, client, 1, "main.anotherFunction", 26)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se = client.ExpectStoppedEvent(t)
+					checkHitBreakpointIds(t, se, "function breakpoint", functionBps[1].Id)
+					checkStop(t, client, 1, "main.anotherFunction", 27)
+				},
+				disconnect: true,
+			}})
+	})
+}
+
 func stringContainsCaseInsensitive(got, want string) bool {
 	return strings.Contains(strings.ToLower(got), strings.ToLower(want))
 }
@@ -3408,7 +3552,6 @@ func TestBadAccess(t *testing.T) {
 						if eInfo.Body.ExceptionId != "runtime error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
 							t.Errorf("\ngot  %#v\nwant ExceptionId=\"runtime error\" Text=\"%s\"", eInfo, errorPrefix)
 						}
-
 					}
 
 					client.ContinueRequest(1)
@@ -3520,6 +3663,41 @@ func TestPanicBreakpointOnNext(t *testing.T) {
 }
 
 func TestFatalThrowBreakpoint(t *testing.T) {
+	runTest(t, "fatalerror", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{3},
+			[]onBreakpoint{{
+				execute: func() {
+					checkStop(t, client, 1, "main.main", 3)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+
+					se := client.ExpectStoppedEvent(t)
+					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "fatal error" {
+						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"fatal error\"", se)
+					}
+
+					// TODO(suzmue): Enable this test for 1.17 when https://github.com/golang/go/issues/46425 is fixed.
+					errorPrefix := "\"go of nil func value\""
+					if goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
+						errorPrefix = "Throw reason unavailable, see https://github.com/golang/go/issues/46425"
+					}
+					client.ExceptionInfoRequest(1)
+					eInfo := client.ExpectExceptionInfoResponse(t)
+					if eInfo.Body.ExceptionId != "fatal error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
+						t.Errorf("\ngot  %#v\nwant ExceptionId=\"runtime error\" Text=%s", eInfo, errorPrefix)
+					}
+
+				},
+				disconnect: true,
+			}})
+	})
 	runTest(t, "testdeadlock", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSessionWithBPs(t, client, "launch",
 			// Launch
@@ -3539,6 +3717,10 @@ func TestFatalThrowBreakpoint(t *testing.T) {
 					if se.Body.Reason != "exception" || se.Body.Description != "fatal error" {
 						t.Errorf("\ngot  %#v\nwant Reason=\"exception\" Description=\"fatal error\"", se)
 					}
+
+					// TODO(suzmue): Get the exception info for the thread and check the description
+					// includes "all goroutines are asleep - deadlock!".
+					// Stopped events with no selected goroutines need to be supported to test deadlock.
 				},
 				disconnect: true,
 			}})
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index a59e13df..5ee3151b 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1565,7 +1565,7 @@ func TestClientServer_FpRegisters(t *testing.T) {
 			if regtest.name == "XMM11" && !avx2 {
 				continue
 			}
-			if regtest.name == "XMM12" && !avx512 {
+			if regtest.name == "XMM12" && (!avx512 || testBackend == "rr") {
 				continue
 			}
 			found := false
@@ -2391,3 +2391,64 @@ func TestStopServerWithClosedListener(t *testing.T) {
 	listener.Close()
 	time.Sleep(1 * time.Second) // give time to server to panic
 }
+
+func TestGoroutinesGrouping(t *testing.T) {
+	// Tests the goroutine grouping and filtering feature
+	withTestClient2("goroutinegroup", t, func(c service.Client) {
+		state := <-c.Continue()
+		assertNoError(state.Err, t, "Continue")
+		_, ggrp, _, _, err := c.ListGoroutinesWithFilter(0, 0, nil, &api.GoroutineGroupingOptions{GroupBy: api.GoroutineLabel, GroupByKey: "name", MaxGroupMembers: 5, MaxGroups: 10})
+		assertNoError(err, t, "ListGoroutinesWithFilter (group by label)")
+		t.Logf("%#v\n", ggrp)
+		if len(ggrp) < 5 {
+			t.Errorf("not enough groups %d\n", len(ggrp))
+		}
+		var unnamedCount int
+		for i := range ggrp {
+			if ggrp[i].Name == "name=" {
+				unnamedCount = ggrp[i].Total
+				break
+			}
+		}
+		gs, _, _, _, err := c.ListGoroutinesWithFilter(0, 0, []api.ListGoroutinesFilter{{Kind: api.GoroutineLabel, Arg: "name="}}, nil)
+		assertNoError(err, t, "ListGoroutinesWithFilter (filter unnamed)")
+		if len(gs) != unnamedCount {
+			t.Errorf("wrong number of goroutines returned by filter: %d (expected %d)\n", len(gs), unnamedCount)
+		}
+	})
+}
+
+func TestLongStringArg(t *testing.T) {
+	// Test the ability to load more elements of a string argument, this could
+	// be broken if registerized variables are not handled correctly.
+	withTestClient2("morestringarg", t, func(c service.Client) {
+		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.f"})
+		assertNoError(err, t, "CreateBreakpoint")
+		state := <-c.Continue()
+		assertNoError(state.Err, t, "Continue")
+
+		test := func(name, val1, val2 string) uint64 {
+			var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, name, normalLoadConfig)
+			assertNoError(err, t, "EvalVariable")
+			t.Logf("%#v\n", var1)
+			if var1.Value != val1 {
+				t.Fatalf("wrong value for variable: %q", var1.Value)
+			}
+			var2, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, fmt.Sprintf("(*(*%q)(%#x))[64:]", var1.Type, var1.Addr), normalLoadConfig)
+			assertNoError(err, t, "EvalVariable")
+			t.Logf("%#v\n", var2)
+			if var2.Value != val2 {
+				t.Fatalf("wrong value for variable: %q", var2.Value)
+
+			}
+			return var1.Addr
+		}
+
+		saddr := test("s", "very long string 01234567890123456789012345678901234567890123456", "7890123456789012345678901234567890123456789X")
+		test("q", "very long string B 012345678901234567890123456789012345678901234", "567890123456789012345678901234567890123456789X2")
+		saddr2 := test("s", "very long string 01234567890123456789012345678901234567890123456", "7890123456789012345678901234567890123456789X")
+		if saddr != saddr2 {
+			t.Fatalf("address of s changed (%#x %#x)", saddr, saddr2)
+		}
+	})
+}
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index a40e9710..af6d4c78 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -86,13 +86,13 @@ func findFirstNonRuntimeFrame(p *proc.Target) (proc.Stackframe, error) {
 
 func evalScope(p *proc.Target) (*proc.EvalScope, error) {
 	if testBackend != "rr" {
-		return proc.GoroutineScope(p.CurrentThread())
+		return proc.GoroutineScope(p, p.CurrentThread())
 	}
 	frame, err := findFirstNonRuntimeFrame(p)
 	if err != nil {
 		return nil, err
 	}
-	return proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame), nil
+	return proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame), nil
 }
 
 func evalVariable(p *proc.Target, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
@@ -111,7 +111,7 @@ func (tc *varTest) alternateVarTest() varTest {
 }
 
 func setVariable(p *proc.Target, symbol, value string) error {
-	scope, err := proc.GoroutineScope(p.CurrentThread())
+	scope, err := proc.GoroutineScope(p, p.CurrentThread())
 	if err != nil {
 		return err
 	}
@@ -471,10 +471,10 @@ func TestLocalVariables(t *testing.T) {
 				var frame proc.Stackframe
 				frame, err = findFirstNonRuntimeFrame(p)
 				if err == nil {
-					scope = proc.FrameToScope(p.BinInfo(), p.Memory(), nil, frame)
+					scope = proc.FrameToScope(p, p.BinInfo(), p.Memory(), nil, frame)
 				}
 			} else {
-				scope, err = proc.GoroutineScope(p.CurrentThread())
+				scope, err = proc.GoroutineScope(p, p.CurrentThread())
 			}
 
 			assertNoError(err, t, "scope")
@@ -1152,7 +1152,7 @@ func TestIssue1075(t *testing.T) {
 		setFunctionBreakpoint(p, t, "net/http.(*Client).Do")
 		assertNoError(p.Continue(), t, "Continue()")
 		for i := 0; i < 10; i++ {
-			scope, err := proc.GoroutineScope(p.CurrentThread())
+			scope, err := proc.GoroutineScope(p, p.CurrentThread())
 			assertNoError(err, t, fmt.Sprintf("GoroutineScope (%d)", i))
 			vars, err := scope.LocalVariables(pnormalLoadConfig)
 			assertNoError(err, t, fmt.Sprintf("LocalVariables (%d)", i))
@@ -1377,7 +1377,7 @@ func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
 	}
 
 	if varExpr != "" {
-		scope, err := proc.GoroutineScope(p.CurrentThread())
+		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 		assertNoError(err, t, "GoroutineScope")
 		v, err := scope.EvalExpression(varExpr, pnormalLoadConfig)
 		assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", varExpr))