Repository: go-delve/delve
Pull request #2524 (2021-06-02 21:57:41)

service/dap: add throw reason to exception info
We can get the throw reason by looking at the argument "s" in runtime.throw. This is not currently working in Go 1.16 or Go 1.17 (see golang/go#46425), but does work in Go 1.15 and Go 1.14


suzmue: Screen shot of fatal throw reason:
![Screen Shot 2021-06-02 at 5 57 55 PM](https://user-images.githubusercontent.com/6634754/120557539-151e8b00-c3cc-11eb-88f3-39031d35062f.png)

aarzilli: See test failures.

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/_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/longstrings.go b/_fixtures/longstrings.go
new file mode 100644
index 00000000..8ad14dbe
--- /dev/null
+++ b/_fixtures/longstrings.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"runtime"
+)
+
+func buildString(length int) string {
+	s := ""
+	for i := 0; i < length; i++ {
+		s = s + "x"
+	}
+	return s
+}
+
+func main() {
+	s513 := buildString(513)
+	s1025 := buildString(1025)
+	s4097 := buildString(4097)
+	nested := map[int]string{513: s513, 1025: s1025, 4097: s4097}
+	runtime.Breakpoint()
+	_ = nested
+}
diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go
index 09dd6de7..e330ae4c 100644
--- a/pkg/dwarf/line/line_parser.go
+++ b/pkg/dwarf/line/line_parser.go
@@ -39,6 +39,9 @@ type DebugLineInfo struct {
 	// lastMachineCache[pc] is a state machine stopped at an address after pc
 	lastMachineCache map[uint64]*StateMachine
 
+	// debugLineStr is the contents of the .debug_line_str section.
+	debugLineStr []byte
+
 	// staticBase is the address at which the executable is loaded, 0 for non-PIEs
 	staticBase uint64
 
@@ -59,7 +62,7 @@ type FileEntry struct {
 type DebugLines []*DebugLineInfo
 
 // ParseAll parses all debug_line segments found in data
-func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
+func ParseAll(data []byte, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
 	var (
 		lines = make(DebugLines, 0)
 		buf   = bytes.NewBuffer(data)
@@ -67,7 +70,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
 
 	// We have to parse multiple file name tables here.
 	for buf.Len() > 0 {
-		lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash, ptrSize))
+		lines = append(lines, Parse("", buf, debugLineStr, logfn, staticBase, normalizeBackslash, ptrSize))
 	}
 
 	return lines
@@ -75,9 +78,12 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
 
 // Parse parses a single debug_line segment from buf. Compdir is the
 // DW_AT_comp_dir attribute of the associated compile unit.
-func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
+func Parse(compdir string, buf *bytes.Buffer, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
 	dbl := new(DebugLineInfo)
 	dbl.Logf = logfn
+	if logfn == nil {
+		dbl.Logf = func(string, ...interface{}) {}
+	}
 	dbl.staticBase = staticBase
 	dbl.ptrSize = ptrSize
 	dbl.Lookup = make(map[string]*FileEntry)
@@ -86,6 +92,7 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{})
 	dbl.stateMachineCache = make(map[uint64]*StateMachine)
 	dbl.lastMachineCache = make(map[uint64]*StateMachine)
 	dbl.normalizeBackslash = normalizeBackslash
+	dbl.debugLineStr = debugLineStr
 
 	parseDebugLinePrologue(dbl, buf)
 	if dbl.Prologue.Version >= 5 {
@@ -123,10 +130,9 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
 		dbl.ptrSize += int(buf.Next(1)[0]) // segment_selector_size
 	}
 
-	// Version 4 or earlier
 	p.Length = binary.LittleEndian.Uint32(buf.Next(4))
 	p.MinInstrLength = uint8(buf.Next(1)[0])
-	if p.Version == 4 {
+	if p.Version >= 4 {
 		p.MaxOpPerInstr = uint8(buf.Next(1)[0])
 	} else {
 		p.MaxOpPerInstr = 1
@@ -174,10 +180,14 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool {
 		for dirEntryFormReader.next(buf) {
 			switch dirEntryFormReader.contentType {
 			case _DW_LNCT_path:
-				if dirEntryFormReader.formCode != _DW_FORM_string {
+				switch dirEntryFormReader.formCode {
+				case _DW_FORM_string:
 					info.IncludeDirs = append(info.IncludeDirs, dirEntryFormReader.str)
-				} else {
-					//TODO(aarzilli): support debug_string, debug_line_str
+				case _DW_FORM_line_strp:
+					buf := bytes.NewBuffer(info.debugLineStr[dirEntryFormReader.u64:])
+					dir, _ := util.ParseString(buf)
+					info.IncludeDirs = append(info.IncludeDirs, dir)
+				default:
 					info.Logf("unsupported string form %#x", dirEntryFormReader.formCode)
 				}
 			case _DW_LNCT_directory_index:
@@ -277,10 +287,13 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool {
 
 			switch fileEntryFormReader.contentType {
 			case _DW_LNCT_path:
-				if fileEntryFormReader.formCode != _DW_FORM_string {
+				switch fileEntryFormReader.formCode {
+				case _DW_FORM_string:
 					p = fileEntryFormReader.str
-				} else {
-					//TODO(aarzilli): support debug_string, debug_line_str
+				case _DW_FORM_line_strp:
+					buf := bytes.NewBuffer(info.debugLineStr[fileEntryFormReader.u64:])
+					p, _ = util.ParseString(buf)
+				default:
 					info.Logf("unsupported string form %#x", fileEntryFormReader.formCode)
 				}
 			case _DW_LNCT_directory_index:
diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go
index 6f96569e..867d9b7b 100644
--- a/pkg/dwarf/line/parse_util.go
+++ b/pkg/dwarf/line/parse_util.go
@@ -165,13 +165,14 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
 		}
 		rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0)))
 
+	case ^uint64(0):
+		// do nothing
+
 	default:
 		if rdr.logf != nil {
 			rdr.logf("unknown form code %#x", rdr.formCode)
 		}
 		rdr.formCodes[rdr.nexti] = ^uint64(0) // only print error once
-	case ^uint64(0):
-		// do nothing
 	}
 
 	rdr.nexti++
diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go
index d7e11c26..04501a97 100644
--- a/pkg/dwarf/line/state_machine.go
+++ b/pkg/dwarf/line/state_machine.go
@@ -103,9 +103,13 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *Stat
 	for op := range standardopcodes {
 		opcodes[op] = standardopcodes[op]
 	}
+	var file string
+	if len(dbl.FileNames) > 0 {
+		file = dbl.FileNames[0].Path
+	}
 	sm := &StateMachine{
 		dbl:         dbl,
-		file:        dbl.FileNames[0].Path,
+		file:        file,
 		line:        1,
 		buf:         bytes.NewBuffer(instructions),
 		opcodes:     opcodes,
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index b8af4e91..a7e0ea69 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -645,11 +645,12 @@ type Image struct {
 	closer         io.Closer
 	sepDebugCloser io.Closer
 
-	dwarf       *dwarf.Data
-	dwarfReader *dwarf.Reader
-	loclist2    *loclist.Dwarf2Reader
-	loclist5    *loclist.Dwarf5Reader
-	debugAddr   *godwarf.DebugAddrSection
+	dwarf        *dwarf.Data
+	dwarfReader  *dwarf.Reader
+	loclist2     *loclist.Dwarf2Reader
+	loclist5     *loclist.Dwarf5Reader
+	debugAddr    *godwarf.DebugAddrSection
+	debugLineStr []byte
 
 	typeCache map[dwarf.Offset]godwarf.Type
 
@@ -1202,6 +1203,8 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
 	image.loclist5 = loclist.NewDwarf5Reader(debugLoclistBytes)
 	debugAddrBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "addr")
 	image.debugAddr = godwarf.ParseAddr(debugAddrBytes)
+	debugLineStrBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "line_str")
+	image.debugLineStr = debugLineStrBytes
 
 	wg.Add(3)
 	go bi.parseDebugFrameElf(image, dwarfFile, debugInfoBytes, wg)
@@ -1723,7 +1726,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB
 						logger.Printf(fmt, args)
 					}
 				}
-				cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize())
+				cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), image.debugLineStr, logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize())
 			}
 			cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
 			if cu.isgo && cu.producer != "" {
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index fa35095d..6d33ec05 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -1950,6 +1950,31 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
 	return nil, fmt.Errorf("key not found")
 }
 
+// LoadResliced returns a new array, slice or map that starts at index start and contains
+// up to cfg.MaxArrayValues children.
+func (v *Variable) LoadResliced(start int, cfg LoadConfig) (newV *Variable, err error) {
+	switch v.Kind {
+	case reflect.Array, reflect.Slice:
+		low, high := int64(start), int64(start+cfg.MaxArrayValues)
+		if high > v.Len {
+			high = v.Len
+		}
+		newV, err = v.reslice(low, high)
+		if err != nil {
+			return nil, err
+		}
+	case reflect.Map:
+		newV = v.clone()
+		newV.Children = nil
+		newV.loaded = false
+		newV.mapSkip = start
+	default:
+		return nil, fmt.Errorf("variable to reslice is not an array, slice, or map")
+	}
+	newV.loadValue(cfg)
+	return newV, nil
+}
+
 func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
 	wrong := false
 	cptrNeedsFakeSlice := false
diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go
index 3b8f17c6..8a4cf606 100644
--- a/pkg/proc/interface.go
+++ b/pkg/proc/interface.go
@@ -26,6 +26,10 @@ type Process interface {
 // the `proc` package.
 // This is temporary and in support of an ongoing refactor.
 type ProcessInternal interface {
+	// Valid returns true if this Process can be used. When it returns false it
+	// also returns an error describing why the Process is invalid (either
+	// ErrProcessExited or ErrProcessDetached).
+	Valid() (bool, error)
 	// Restart restarts the recording from the specified position, or from the
 	// last checkpoint if pos == "".
 	// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
@@ -87,10 +91,6 @@ type Info interface {
 	// ResumeNotify specifies a channel that will be closed the next time
 	// ContinueOnce finishes resuming the target.
 	ResumeNotify(chan<- struct{})
-	// Valid returns true if this Process can be used. When it returns false it
-	// also returns an error describing why the Process is invalid (either
-	// ErrProcessExited or ErrProcessDetached).
-	Valid() (bool, error)
 	BinInfo() *BinaryInfo
 	EntryPoint() (uint64, error)
 
diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go
index 1befb68f..68a7338a 100644
--- a/pkg/proc/stack.go
+++ b/pkg/proc/stack.go
@@ -102,18 +102,13 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
 		so := thread.BinInfo().PCToImage(regs.PC())
 		dwarfRegs := *(thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs))
 		dwarfRegs.ChangeFunc = thread.SetReg
-		it := newStackIterator(thread.BinInfo(), thread.ProcessMemory(), dwarfRegs, 0, nil, -1, nil, 0)
+		it := newStackIterator(thread.BinInfo(), thread.ProcessMemory(), dwarfRegs, 0, nil, 0)
 		return it.stacktrace(depth)
 	}
 	return g.Stacktrace(depth, 0)
 }
 
 func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
-	stkbar, err := g.stkbar()
-	if err != nil {
-		return nil, err
-	}
-
 	bi := g.variable.bi
 	if g.Thread != nil {
 		regs, err := g.Thread.Registers()
@@ -126,13 +121,13 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
 		return newStackIterator(
 			bi, g.variable.mem,
 			dwarfRegs,
-			g.stack.hi, stkbar, g.stkbarPos, g, opts), nil
+			g.stack.hi, g, opts), nil
 	}
 	so := g.variable.bi.PCToImage(g.PC)
 	return newStackIterator(
 		bi, g.variable.mem,
 		bi.Arch.addrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
-		g.stack.hi, stkbar, g.stkbarPos, g, opts), nil
+		g.stack.hi, g, opts), nil
 }
 
 type StacktraceOptions uint16
@@ -187,10 +182,8 @@ type stackIterator struct {
 	mem   MemoryReadWriter
 	err   error
 
-	stackhi        uint64
-	systemstack    bool
-	stackBarrierPC uint64
-	stkbar         []savedLR
+	stackhi     uint64
+	systemstack bool
 
 	// regs is the register set for the current frame
 	regs op.DwarfRegisters
@@ -202,36 +195,12 @@ type stackIterator struct {
 	opts StacktraceOptions
 }
 
-type savedLR struct {
-	ptr uint64
-	val uint64
-}
-
-func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int, g *G, opts StacktraceOptions) *stackIterator {
-	stackBarrierFunc := bi.LookupFunc["runtime.stackBarrier"] // stack barriers were removed in Go 1.9
-	var stackBarrierPC uint64
-	if stackBarrierFunc != nil && stkbar != nil {
-		stackBarrierPC = stackBarrierFunc.Entry
-		fn := bi.PCToFunc(regs.PC())
-		if fn != nil && fn.Name == "runtime.stackBarrier" {
-			// We caught the goroutine as it's executing the stack barrier, we must
-			// determine whether or not g.stackPos has already been incremented or not.
-			if len(stkbar) > 0 && stkbar[stkbarPos].ptr < regs.SP() {
-				// runtime.stackBarrier has not incremented stkbarPos.
-			} else if stkbarPos > 0 && stkbar[stkbarPos-1].ptr < regs.SP() {
-				// runtime.stackBarrier has incremented stkbarPos.
-				stkbarPos--
-			} else {
-				return &stackIterator{err: fmt.Errorf("failed to unwind through stackBarrier at SP %x", regs.SP())}
-			}
-		}
-		stkbar = stkbar[stkbarPos:]
-	}
+func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, g *G, opts StacktraceOptions) *stackIterator {
 	systemstack := true
 	if g != nil {
 		systemstack = g.SystemStack
 	}
-	return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, opts: opts}
+	return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, systemstack: systemstack, g: g, opts: opts}
 }
 
 // Next points the iterator to the next stack frame.
@@ -243,12 +212,6 @@ func (it *stackIterator) Next() bool {
 	callFrameRegs, ret, retaddr := it.advanceRegs()
 	it.frame = it.newStackframe(ret, retaddr)
 
-	if it.stkbar != nil && it.frame.Ret == it.stackBarrierPC && it.frame.addrret == it.stkbar[0].ptr {
-		// Skip stack barrier frames
-		it.frame.Ret = it.stkbar[0].val
-		it.stkbar = it.stkbar[1:]
-	}
-
 	if it.opts&StacktraceSimple == 0 {
 		if it.bi.Arch.switchStack(it, &callFrameRegs) {
 			return true
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index a923bec6..fc61a7f0 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -62,6 +62,10 @@ type Target struct {
 	// This must be cleared whenever the target is resumed.
 	gcache goroutineCache
 	iscgo  *bool
+
+	// exitStatus is the exit status of the process we are debugging.
+	// Saved here to relay to any future commands.
+	exitStatus int
 }
 
 // ErrProcessExited indicates that the process has exited and contains both
@@ -203,6 +207,20 @@ func (t *Target) IsCgo() bool {
 	return false
 }
 
+// Valid returns true if this Process can be used. When it returns false it
+// also returns an error describing why the Process is invalid (either
+// ErrProcessExited or ErrProcessDetached).
+func (t *Target) Valid() (bool, error) {
+	ok, err := t.proc.Valid()
+	if !ok && err != nil {
+		if pe, ok := err.(ErrProcessExited); ok {
+			pe.Status = t.exitStatus
+			err = pe
+		}
+	}
+	return ok, err
+}
+
 // SupportsFunctionCalls returns whether or not the backend supports
 // calling functions during a debug session.
 // Currently only non-recorded processes running on AMD64 support
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index 068dee9a..0106c5b6 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -83,6 +83,9 @@ func (dbp *Target) Continue() error {
 					dbp.selectedGoroutine, _ = GetG(curth)
 				}
 			}
+			if pe, ok := err.(ErrProcessExited); ok {
+				dbp.exitStatus = pe.Status
+			}
 			return err
 		}
 		if dbp.StopReason == StopLaunched {
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 76bd4471..2190f7e2 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -190,17 +190,15 @@ const (
 // G represents a runtime G (goroutine) structure (at least the
 // fields that Delve is interested in).
 type G struct {
-	ID        int    // Goroutine ID
-	PC        uint64 // PC of goroutine when it was parked.
-	SP        uint64 // SP of goroutine when it was parked.
-	BP        uint64 // BP of goroutine when it was parked (go >= 1.7).
-	LR        uint64 // LR of goroutine when it was parked.
-	GoPC      uint64 // PC of 'go' statement that created this goroutine.
-	StartPC   uint64 // PC of the first function run on this goroutine.
-	Status    uint64
-	stkbarVar *Variable // stkbar field of g struct
-	stkbarPos int       // stkbarPos field of g struct
-	stack     stack     // value of stack
+	ID      int    // Goroutine ID
+	PC      uint64 // PC of goroutine when it was parked.
+	SP      uint64 // SP of goroutine when it was parked.
+	BP      uint64 // BP of goroutine when it was parked (go >= 1.7).
+	LR      uint64 // LR of goroutine when it was parked.
+	GoPC    uint64 // PC of 'go' statement that created this goroutine.
+	StartPC uint64 // PC of the first function run on this goroutine.
+	Status  uint64
+	stack   stack // value of stack
 
 	WaitSince  int64
 	WaitReason int64
@@ -875,13 +873,6 @@ func (v *Variable) parseG() (*G, error) {
 		}
 	}
 
-	stkbarVar := v.loadFieldNamed("stkbar")
-	stkbarVarPosFld := v.loadFieldNamed("stkbarPos")
-	var stkbarPos int64
-	if stkbarVarPosFld != nil { // stack barriers were removed in Go 1.9
-		stkbarPos, _ = constant.Int64Val(stkbarVarPosFld.Value)
-	}
-
 	status := loadInt64Maybe("atomicstatus")
 
 	if unreadable {
@@ -905,8 +896,6 @@ func (v *Variable) parseG() (*G, error) {
 		WaitReason: waitReason,
 		CurrentLoc: Location{PC: uint64(pc), File: f, Line: l, Fn: fn},
 		variable:   v,
-		stkbarVar:  stkbarVar,
-		stkbarPos:  int(stkbarPos),
 		stack:      stack{hi: stackhi, lo: stacklo},
 	}
 	return g, nil
@@ -1026,31 +1015,6 @@ func (a *Ancestor) Stack(n int) ([]Stackframe, error) {
 	return r, nil
 }
 
-// Returns the list of saved return addresses used by stack barriers
-func (g *G) stkbar() ([]savedLR, error) {
-	if g.stkbarVar == nil { // stack barriers were removed in Go 1.9
-		return nil, nil
-	}
-	g.stkbarVar.loadValue(LoadConfig{false, 1, 0, int(g.stkbarVar.Len), 3, 0})
-	if g.stkbarVar.Unreadable != nil {
-		return nil, fmt.Errorf("unreadable stkbar: %v", g.stkbarVar.Unreadable)
-	}
-	r := make([]savedLR, len(g.stkbarVar.Children))
-	for i, child := range g.stkbarVar.Children {
-		for _, field := range child.Children {
-			switch field.Name {
-			case "savedLRPtr":
-				ptr, _ := constant.Int64Val(field.Value)
-				r[i].ptr = uint64(ptr)
-			case "savedLRVal":
-				val, _ := constant.Int64Val(field.Value)
-				r[i].val = uint64(val)
-			}
-		}
-	}
-	return r, nil
-}
-
 func (v *Variable) structMember(memberName string) (*Variable, error) {
 	if v.Unreadable != nil {
 		return v.clone(), nil
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index fa0e4f11..8c4e36d3 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -1308,7 +1308,7 @@ func scopePrefixSwitch(t *Term, ctx callContext) error {
 
 func exitedToError(state *api.DebuggerState, err error) (*api.DebuggerState, error) {
 	if err == nil && state.Exited {
-		return nil, fmt.Errorf("Process has exited with status %d", state.ExitStatus)
+		return nil, fmt.Errorf("Process %d has exited with status %d", state.Pid, state.ExitStatus)
 	}
 	return state, err
 }
@@ -2331,68 +2331,8 @@ func digits(n int) int {
 	return int(math.Floor(math.Log10(float64(n)))) + 1
 }
 
-const stacktraceTruncatedMessage = "(truncated)"
-
 func printStack(t *Term, out io.Writer, stack []api.Stackframe, ind string, offsets bool) {
-	PrintStack(t.formatPath, out, stack, ind, offsets)
-}
-
-func PrintStack(formatPath func(string) string, out io.Writer, stack []api.Stackframe, ind string, offsets bool) {
-	if len(stack) == 0 {
-		return
-	}
-
-	extranl := offsets
-	for i := range stack {
-		if extranl {
-			break
-		}
-		extranl = extranl || (len(stack[i].Defers) > 0) || (len(stack[i].Arguments) > 0) || (len(stack[i].Locals) > 0)
-	}
-
-	d := digits(len(stack) - 1)
-	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
-	s := ind + strings.Repeat(" ", d+2+len(ind))
-
-	for i := range stack {
-		if stack[i].Err != "" {
-			fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
-			continue
-		}
-		fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
-		fmt.Fprintf(out, "%sat %s:%d\n", s, formatPath(stack[i].File), stack[i].Line)
-
-		if offsets {
-			fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
-		}
-
-		for j, d := range stack[i].Defers {
-			deferHeader := fmt.Sprintf("%s    defer %d: ", s, j+1)
-			s2 := strings.Repeat(" ", len(deferHeader))
-			if d.Unreadable != "" {
-				fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
-				continue
-			}
-			fmt.Fprintf(out, "%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
-			fmt.Fprintf(out, "%sat %s:%d\n", s2, formatPath(d.DeferredLoc.File), d.DeferredLoc.Line)
-			fmt.Fprintf(out, "%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), formatPath(d.DeferLoc.File), d.DeferLoc.Line)
-		}
-
-		for j := range stack[i].Arguments {
-			fmt.Fprintf(out, "%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
-		}
-		for j := range stack[i].Locals {
-			fmt.Fprintf(out, "%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
-		}
-
-		if extranl {
-			fmt.Fprintln(out)
-		}
-	}
-
-	if len(stack) > 0 && !stack[len(stack)-1].Bottom {
-		fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
-	}
+	api.PrintStack(t.formatPath, out, stack, ind, offsets, func(api.Stackframe) bool { return true })
 }
 
 func printcontext(t *Term, state *api.DebuggerState) {
diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go
index b230ba24..02b3ff50 100644
--- a/service/api/prettyprint.go
+++ b/service/api/prettyprint.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"fmt"
 	"io"
+	"math"
 	"reflect"
 	"strconv"
 	"strings"
@@ -493,3 +494,73 @@ func byteArrayToUInt64(buf []byte, isLittleEndian bool) uint64 {
 	}
 	return n
 }
+
+const stacktraceTruncatedMessage = "(truncated)"
+
+func digits(n int) int {
+	if n <= 0 {
+		return 1
+	}
+	return int(math.Floor(math.Log10(float64(n)))) + 1
+}
+
+func PrintStack(formatPath func(string) string, out io.Writer, stack []Stackframe, ind string, offsets bool, include func(Stackframe) bool) {
+	if len(stack) == 0 {
+		return
+	}
+
+	extranl := offsets
+	for i := range stack {
+		if extranl {
+			break
+		}
+		extranl = extranl || (len(stack[i].Defers) > 0) || (len(stack[i].Arguments) > 0) || (len(stack[i].Locals) > 0)
+	}
+
+	d := digits(len(stack) - 1)
+	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
+	s := ind + strings.Repeat(" ", d+2+len(ind))
+
+	for i := range stack {
+		if !include(stack[i]) {
+			continue
+		}
+		if stack[i].Err != "" {
+			fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
+			continue
+		}
+		fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
+		fmt.Fprintf(out, "%sat %s:%d\n", s, formatPath(stack[i].File), stack[i].Line)
+
+		if offsets {
+			fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
+		}
+
+		for j, d := range stack[i].Defers {
+			deferHeader := fmt.Sprintf("%s    defer %d: ", s, j+1)
+			s2 := strings.Repeat(" ", len(deferHeader))
+			if d.Unreadable != "" {
+				fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
+				continue
+			}
+			fmt.Fprintf(out, "%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
+			fmt.Fprintf(out, "%sat %s:%d\n", s2, formatPath(d.DeferredLoc.File), d.DeferredLoc.Line)
+			fmt.Fprintf(out, "%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), formatPath(d.DeferLoc.File), d.DeferLoc.Line)
+		}
+
+		for j := range stack[i].Arguments {
+			fmt.Fprintf(out, "%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
+		}
+		for j := range stack[i].Locals {
+			fmt.Fprintf(out, "%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
+		}
+
+		if extranl {
+			fmt.Fprintln(out)
+		}
+	}
+
+	if len(stack) > 0 && !stack[len(stack)-1].Bottom {
+		fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
+	}
+}
diff --git a/service/api/types.go b/service/api/types.go
index f9163237..0767f457 100644
--- a/service/api/types.go
+++ b/service/api/types.go
@@ -17,6 +17,8 @@ var ErrNotExecutable = errors.New("not an executable file")
 
 // DebuggerState represents the current context of the debugger.
 type DebuggerState struct {
+	// PID of the process we are debugging.
+	Pid int
 	// Running is true if the process is running and no other information can be collected.
 	Running bool
 	// Recording is true if the process is currently being recorded and no other
diff --git a/service/dap/handles.go b/service/dap/handles.go
index d01baf91..8c1f9497 100644
--- a/service/dap/handles.go
+++ b/service/dap/handles.go
@@ -24,6 +24,9 @@ type fullyQualifiedVariable struct {
 	fullyQualifiedNameOrExpr string
 	// True if this represents variable scope
 	isScope bool
+	// startIndex is the index of the first child for an array or slice.
+	// This variable represents a chunk of the array, slice or map.
+	startIndex int
 }
 
 func newHandlesMap() *handlesMap {
diff --git a/service/dap/server.go b/service/dap/server.go
index 502d4cea..cad8a880 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -29,10 +29,10 @@ 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"
-	"github.com/go-delve/delve/pkg/terminal"
 	"github.com/go-delve/delve/service"
 	"github.com/go-delve/delve/service/api"
 	"github.com/go-delve/delve/service/debugger"
@@ -166,18 +166,31 @@ type dapClientCapabilites struct {
 	supportsProgressReporting    bool
 }
 
-// DefaultLoadConfig controls how variables are loaded from the target's memory, borrowing the
-// default value from the original vscode-go debug adapter and rpc server.
-// With dlv-dap, users currently do not have a way to adjust these.
-// TODO(polina): Support setting config via launch/attach args or only rely on on-demand loading?
+// DefaultLoadConfig controls how variables are loaded from the target's memory.
+// These limits are conservative to minimize performace overhead for bulk loading.
+// With dlv-dap, users do not have a way to adjust these.
+// Instead we are focusing in interacive loading with nested reloads, array/map
+// paging and context-specific string limits.
 var DefaultLoadConfig = proc.LoadConfig{
 	FollowPointers:     true,
 	MaxVariableRecurse: 1,
-	MaxStringLen:       64,
-	MaxArrayValues:     64,
-	MaxStructFields:    -1,
+	// TODO(polina): consider 1024 limit instead:
+	// - vscode+C appears to use 1024 as the load limit
+	// - vscode viewlet hover truncates at 1023 characters
+	MaxStringLen:    512,
+	MaxArrayValues:  64,
+	MaxStructFields: -1,
 }
 
+const (
+	// When a user examines a single string, we can relax the loading limit.
+	maxSingleStringLen = 4 << 10 // 4096
+	// Results of a call are single-use and transient. We need to maximize
+	// what is presented. A common use case of a call injection is to
+	// stringify complex data conveniently.
+	maxStringLenInCallRetVars = 1 << 10 // 1024
+)
+
 // NewServer creates a new DAP Server. It takes an opened Listener
 // via config and assumes its ownership. config.DisconnectChan has to be set;
 // it will be closed by the server when the client fails to connect,
@@ -971,7 +984,7 @@ func (s *Server) stopDebugSession(killProcess bool) error {
 	// Additional clean-up might be especially critical when we support multiple clients.
 	state, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
 	if err == proc.ErrProcessDetached {
-		s.log.Debug("halt returned error:", err)
+		s.log.Debug("halt returned error: ", err)
 		return nil
 	}
 	if err != nil {
@@ -979,11 +992,14 @@ func (s *Server) stopDebugSession(killProcess bool) error {
 		case proc.ErrProcessExited:
 			exited = err
 		default:
-			s.log.Error("halt returned error:", err)
+			s.log.Error("halt returned error: ", err)
+			if err.Error() == "no such process" {
+				exited = err
+			}
 		}
 	} else if state.Exited {
 		exited = proc.ErrProcessExited{Pid: s.debugger.ProcessPid(), Status: state.ExitStatus}
-		s.log.Debug("halt returned state:", exited)
+		s.log.Debug("halt returned state: ", exited)
 	}
 	if exited != nil {
 		s.logToConsole(exited.Error())
@@ -1057,7 +1073,7 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 			continue
 		}
 		if _, ok := bpAdded[reqString]; ok {
-			err = fmt.Errorf("Breakpoint exists at %q, line: %d, column: %d", request.Arguments.Source.Path, want.Line, want.Column)
+			err = fmt.Errorf("breakpoint exists at %q, line: %d, column: %d", request.Arguments.Source.Path, want.Line, want.Column)
 		} else {
 			got.Cond = want.Condition
 			got.HitCond = want.HitCondition
@@ -1084,7 +1100,7 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 		var got *api.Breakpoint
 		var err error
 		if _, ok := bpAdded[reqString]; ok {
-			err = fmt.Errorf("Breakpoint exists at %q, line: %d, column: %d", request.Arguments.Source.Path, want.Line, want.Column)
+			err = fmt.Errorf("breakpoint exists at %q, line: %d, column: %d", request.Arguments.Source.Path, want.Line, want.Column)
 		} else {
 			// Create new breakpoints.
 			got, err = s.debugger.CreateBreakpoint(
@@ -1152,7 +1168,7 @@ func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpo
 			continue
 		}
 		if _, ok := bpAdded[reqString]; ok {
-			err = fmt.Errorf("Breakpoint exists at function %q", want.Name)
+			err = fmt.Errorf("breakpoint exists at function %q", want.Name)
 		} else {
 			got.Cond = want.Condition
 			got.HitCond = want.HitCondition
@@ -1317,6 +1333,14 @@ func fnName(loc *proc.Location) string {
 	return loc.Fn.Name
 }
 
+func fnPackageName(loc *proc.Location) string {
+	if loc.Fn == nil {
+		// attribute unknown functions to the runtime
+		return "runtime"
+	}
+	return loc.Fn.PackageName()
+}
+
 // onThreadsRequest handles 'threads' request.
 // This is a mandatory request to support.
 // It is sent in response to configurationDone response and stopped events.
@@ -1525,6 +1549,15 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) {
 		return
 	}
 
+	// 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"
+	}
+
 	stackFrames := make([]dap.StackFrame, len(frames))
 	for i, frame := range frames {
 		loc := &frame.Call
@@ -1535,6 +1568,11 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) {
 			stackFrames[i].Source = dap.Source{Name: filepath.Base(clientPath), Path: clientPath}
 		}
 		stackFrames[i].Column = 0
+
+		packageName := fnPackageName(loc)
+		if !isSystemGoroutine && packageName == "runtime" {
+			stackFrames[i].Source.PresentationHint = "deemphasize"
+		}
 	}
 	// Since the backend doesn't support paging, we load all frames up to
 	// pre-configured depth every time and then slice them here per
@@ -1585,7 +1623,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
 		s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error())
 		return
 	}
-	argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true}
+	argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true, 0}
 
 	// Retrieve local variables
 	locals, err := s.debugger.LocalVariables(goid, frame, 0, DefaultLoadConfig)
@@ -1593,7 +1631,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
 		s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list locals", err.Error())
 		return
 	}
-	locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true}
+	locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true, 0}
 
 	scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)}
 	scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}
@@ -1629,7 +1667,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
 		globScope := &fullyQualifiedVariable{&proc.Variable{
 			Name:     fmt.Sprintf("Globals (package %s)", currPkg),
 			Children: slicePtrVarToSliceVar(globals),
-		}, currPkg, true}
+		}, currPkg, true, 0}
 		scopeGlobals := dap.Scope{Name: globScope.Name, VariablesReference: s.variableHandles.create(globScope)}
 		scopes = append(scopes, scopeGlobals)
 	}
@@ -1658,10 +1696,34 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
 		return
 	}
 
-	children, err := s.childrenToDAPVariables(v)
-	if err != nil {
-		s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", err.Error())
-		return
+	// If there is a filter applied, we will need to create a new variable that includes
+	// the values actually needed to load. This cannot be done when loading the parent
+	// node, since it is unknown at that point which children will need to be loaded.
+	if request.Arguments.Filter == "indexed" {
+		var err error
+		v, err = s.maybeLoadResliced(v, request.Arguments.Start, request.Arguments.Count)
+		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),
@@ -1670,6 +1732,30 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
 	s.send(response)
 }
 
+func (s *Server) maybeLoadResliced(v *fullyQualifiedVariable, start, count int) (*fullyQualifiedVariable, error) {
+	if start == 0 && count == len(v.Children) {
+		// If we have already loaded the correct children,
+		// just return the variable.
+		return v, nil
+	}
+	indexedLoadConfig := DefaultLoadConfig
+	indexedLoadConfig.MaxArrayValues = count
+	newV, err := s.debugger.LoadResliced(v.Variable, start, indexedLoadConfig)
+	if err != nil {
+		return nil, err
+	}
+	return &fullyQualifiedVariable{newV, v.fullyQualifiedNameOrExpr, false, start}, nil
+}
+
+func getIndexedVariableCount(c *proc.Variable) int {
+	indexedVars := 0
+	switch c.Kind {
+	case reflect.Array, reflect.Slice, reflect.Map:
+		indexedVars = int(c.Len)
+	}
+	return indexedVars
+}
+
 // childrenToDAPVariables returns the DAP presentation of the referenced variable's children.
 func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variable, error) {
 	// TODO(polina): consider convertVariableToString instead of convertVariable
@@ -1706,18 +1792,22 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 			// Otherwise, we must return separate variables for both.
 			if keyref > 0 && valref > 0 { // Both are not scalars
 				keyvar := dap.Variable{
-					Name:               fmt.Sprintf("[key %d]", kvIndex),
+					Name:               fmt.Sprintf("[key %d]", v.startIndex+kvIndex),
 					EvaluateName:       keyexpr,
 					Type:               keyType,
 					Value:              key,
 					VariablesReference: keyref,
+					IndexedVariables:   getIndexedVariableCount(keyv),
+					NamedVariables:     getNamedVariableCount(keyv),
 				}
 				valvar := dap.Variable{
-					Name:               fmt.Sprintf("[val %d]", kvIndex),
+					Name:               fmt.Sprintf("[val %d]", v.startIndex+kvIndex),
 					EvaluateName:       valexpr,
 					Type:               valType,
 					Value:              val,
 					VariablesReference: valref,
+					IndexedVariables:   getIndexedVariableCount(valv),
+					NamedVariables:     getNamedVariableCount(valv),
 				}
 				children = append(children, keyvar, valvar)
 			} else { // At least one is a scalar
@@ -1732,13 +1822,17 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 					Value:        val,
 				}
 				if keyref != 0 { // key is a type to be expanded
-					if len(key) > DefaultLoadConfig.MaxStringLen {
+					if len(key) > maxMapKeyValueLen {
 						// Truncate and make unique
-						kvvar.Name = fmt.Sprintf("%s... @ %#x", key[0:DefaultLoadConfig.MaxStringLen], keyv.Addr)
+						kvvar.Name = fmt.Sprintf("%s... @ %#x", key[0:maxMapKeyValueLen], keyv.Addr)
 					}
 					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)
 			}
@@ -1746,14 +1840,17 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 	case reflect.Slice, reflect.Array:
 		children = make([]dap.Variable, len(v.Children))
 		for i := range v.Children {
-			cfqname := fmt.Sprintf("%s[%d]", v.fullyQualifiedNameOrExpr, i)
+			idx := v.startIndex + i
+			cfqname := fmt.Sprintf("%s[%d]", v.fullyQualifiedNameOrExpr, idx)
 			cvalue, cvarref := s.convertVariable(&v.Children[i], cfqname)
 			children[i] = dap.Variable{
-				Name:               fmt.Sprintf("[%d]", i),
+				Name:               fmt.Sprintf("[%d]", idx),
 				EvaluateName:       cfqname,
 				Type:               s.getTypeIfSupported(&v.Children[i]),
 				Value:              cvalue,
 				VariablesReference: cvarref,
+				IndexedVariables:   getIndexedVariableCount(&v.Children[i]),
+				NamedVariables:     getNamedVariableCount(&v.Children[i]),
 			}
 		}
 	default:
@@ -1792,12 +1889,60 @@ func (s *Server) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Variab
 				Type:               s.getTypeIfSupported(c),
 				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 ""
@@ -1823,9 +1968,12 @@ func (s *Server) convertVariableToString(v *proc.Variable) string {
 	return val
 }
 
-// defaultMaxValueLen is the max length of a string representation of a compound or reference
-// type variable.
-const defaultMaxValueLen = 1 << 8 // 256
+const (
+	// Limit the length of a string representation of a compound or reference type variable.
+	maxVarValueLen = 1 << 8 // 256
+	// Limit the length of an inlined map key.
+	maxMapKeyValueLen = 64
+)
 
 // Flags for convertVariableWithOpts option.
 type convertVariableFlags uint8
@@ -1846,7 +1994,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 		if opts&skipRef != 0 {
 			return 0
 		}
-		return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/})
+		return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/, 0})
 	}
 	value = api.ConvertVar(v).SinglelineString()
 	if v.Unreadable != nil {
@@ -1855,15 +2003,13 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 
 	// Some of the types might be fully or partially not loaded based on LoadConfig.
 	// Those that are fully missing (e.g. due to hitting MaxVariableRecurse), can be reloaded in place.
-	// Those that are partially missing (e.g. MaxArrayValues from a large array), need a more creative solution
-	// that is still to be determined. For now, clearly communicate when that happens with additional value labels.
-	// TODO(polina): look into layered/paged loading for truncated strings, arrays, maps and structs.
 	var reloadVariable = func(v *proc.Variable, qualifiedNameOrExpr string) (value string) {
 		// We might be loading variables from the frame that's not topmost, so use
 		// frame-independent address-based expression, not fully-qualified name as per
 		// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables.
-		// TODO(polina): Get *proc.Variable object from debugger instead. Export and set v.loaded to false
-		// and call v.loadValue gain. It's more efficient, and it's guaranteed to keep working with generics.
+		// TODO(polina): Get *proc.Variable object from debugger instead. Export a function to set v.loaded to false
+		// and call v.loadValue gain with a different load config. It's more efficient, and it's guaranteed to keep
+		// working with generics.
 		value = api.ConvertVar(v).SinglelineString()
 		typeName := api.PrettyTypeName(v.DwarfType)
 		loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
@@ -1920,7 +2066,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 		if v.Len > int64(len(v.Children)) { // Not fully loaded
 			if v.Base != 0 && len(v.Children) == 0 { // Fully missing
 				value = reloadVariable(v, qualifiedNameOrExpr)
-			} else { // Partially missing (TODO)
+			} else if !s.clientCapabilities.supportsVariablePaging {
 				value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children), v.Len) + value
 			}
 		}
@@ -1931,7 +2077,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 		if v.Len > int64(len(v.Children)/2) { // Not fully loaded
 			if len(v.Children) == 0 { // Fully missing
 				value = reloadVariable(v, qualifiedNameOrExpr)
-			} else { // Partially missing (TODO)
+			} else if !s.clientCapabilities.supportsVariablePaging {
 				value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children)/2, v.Len) + value
 			}
 		}
@@ -1979,9 +2125,12 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 			variablesReference = maybeCreateVariableHandle(v)
 		}
 	}
+
+	// By default, only values of variables that have children can be truncated.
+	// If showFullValue is set, then all value strings are not truncated.
 	canTruncateValue := showFullValue&opts == 0
-	if len(value) > defaultMaxValueLen && canTruncateValue && canHaveRef {
-		value = value[:defaultMaxValueLen] + "..."
+	if len(value) > maxVarValueLen && canTruncateValue && canHaveRef {
+		value = value[:maxVarValueLen] + "..."
 	}
 	return value, variablesReference
 }
@@ -2030,7 +2179,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			}
 			response.Body = dap.EvaluateResponseBody{
 				Result:             strings.TrimRight(retVarsAsStr, ", "),
-				VariablesReference: s.variableHandles.create(&fullyQualifiedVariable{retVarsAsVar, "", false /*not a scope*/}),
+				VariablesReference: s.variableHandles.create(&fullyQualifiedVariable{retVarsAsVar, "", false /*not a scope*/, 0}),
 			}
 		}
 	} else { // {expression}
@@ -2045,9 +2194,9 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 		case "repl", "variables", "hover", "clipboard":
 			if exprVar.Kind == reflect.String {
 				if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
-					// reload string value with a bigger limit.
+					// Reload the string value with a bigger limit.
 					loadCfg := DefaultLoadConfig
-					loadCfg.MaxStringLen = 4 << 10
+					loadCfg.MaxStringLen = maxSingleStringLen
 					if v, err := s.debugger.EvalVariableInScope(goid, frame, 0, request.Arguments.Expression, loadCfg); err != nil {
 						s.log.Debugf("Failed to load more for %v: %v", request.Arguments.Expression, err)
 					} else {
@@ -2057,11 +2206,13 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			}
 		}
 		var opts convertVariableFlags
-		if ctxt == "variables" || ctxt == "hover" || ctxt == "clipboard" {
+		// Send the full value when the context is "clipboard" or "variables" since
+		// these contexts are used to copy the value.
+		if ctxt == "clipboard" || ctxt == "variables" {
 			opts |= showFullValue
 		}
 		exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts)
-		response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef}
+		response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)}
 	}
 	s.send(response)
 }
@@ -2082,13 +2233,11 @@ func (s *Server) doCall(goid, frame int, expr string) (*api.DebuggerState, []*pr
 	}
 	// The return values of injected function calls are volatile.
 	// Load as much useful data as possible.
-	// - String: a common use case of call injection is to stringify complex
-	// data conveniently. That can easily exceed the default limit, 64.
 	// TODO: investigate whether we need to increase other limits. For example,
 	// the return value is a pointer to a temporary object, which can become
 	// invalid by other injected function calls. Do we care about such use cases?
 	loadCfg := DefaultLoadConfig
-	loadCfg.MaxStringLen = 1 << 10
+	loadCfg.MaxStringLen = maxStringLenInCallRetVars
 
 	// TODO(polina): since call will resume execution of all goroutines,
 	// we should do this asynchronously and send a continued event to the
@@ -2361,14 +2510,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 {
@@ -2397,14 +2568,25 @@ 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
 			fmt.Fprintln(&buf, "Stack:")
-			terminal.PrintStack(s.toClientPath, &buf, apiFrames, "\t", false)
+			userLoc := g.UserCurrent()
+			userFuncPkg := fnPackageName(&userLoc)
+			api.PrintStack(s.toClientPath, &buf, apiFrames, "\t", false, func(s api.Stackframe) bool {
+				// Include all stack frames if the stack trace is for a system goroutine,
+				// otherwise, skip runtime stack frames.
+				if userFuncPkg == "runtime" {
+					return true
+				}
+				return s.Location.Function != nil && !strings.HasPrefix(s.Location.Function.Name(), "runtime.")
+			})
 			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),
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index e4505b31..431ed1be 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 {
@@ -1189,11 +1195,12 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
 	}
 
 	if err != nil {
-		if exitedErr, exited := err.(proc.ErrProcessExited); command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread && exited {
+		if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread {
 			state := &api.DebuggerState{}
+			state.Pid = d.target.Pid()
 			state.Exited = true
-			state.ExitStatus = exitedErr.Status
-			state.Err = errors.New(exitedErr.Error())
+			state.ExitStatus = pe.Status
+			state.Err = pe
 			return state, nil
 		}
 		return nil, err
@@ -1464,6 +1471,14 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol str
 	return s.EvalVariable(symbol, cfg)
 }
 
+// LoadResliced will attempt to 'reslice' a map, array or slice so that the values
+// up to cfg.MaxArrayValues children are loaded starting from index start.
+func (d *Debugger) LoadResliced(v *proc.Variable, start int, cfg proc.LoadConfig) (*proc.Variable, error) {
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+	return v.LoadResliced(start, cfg)
+}
+
 // SetVariableInScope will set the value of the variable represented by
 // 'symbol' to the value given, in the given scope.
 func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, value string) error {
diff --git a/service/internal/sameuser/sameuser_linux.go b/service/internal/sameuser/sameuser_linux.go
index 28e4755c..d8ad5712 100644
--- a/service/internal/sameuser/sameuser_linux.go
+++ b/service/internal/sameuser/sameuser_linux.go
@@ -1,4 +1,5 @@
-//+build linux
+//go:build linux
+// +build linux
 
 package sameuser
 
@@ -56,7 +57,11 @@ func sameUserForHexLocalAddr(filename, hexaddr string) (bool, error) {
 		if localAddr != hexaddr {
 			continue
 		}
-		return uid == int(remoteUID), nil
+		same := uid == int(remoteUID)
+		if !same && logflags.Any() {
+			log.Printf("connection from different user (remote: %d, local: %d) detected: %v", remoteUID, uid, line)
+		}
+		return same, nil
 	}
 	return false, &errConnectionNotFound{filename}
 }


Test patch:
diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go
index d59cbebc..57174831 100644
--- a/_fixtures/testvariables2.go
+++ b/_fixtures/testvariables2.go
@@ -328,7 +328,9 @@ func main() {
 
 	longstr := "very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"
 	m5 := map[C]int{{longstr}: 1}
-	m6 := C{s: longstr}
+	m6 := map[string]int{longstr: 123}
+	m7 := map[C]C{{longstr}: {"hello"}}
+	cl := C{s: longstr}
 	var nilstruct *astruct = nil
 
 	val := A{val: 1} // val vs val.val
@@ -363,5 +365,5 @@ func main() {
 	longslice := make([]int, 100, 100)
 
 	runtime.Breakpoint()
-	fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6)
+	fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl)
 }
diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go
index cbb5b71c..043a16bf 100644
--- a/pkg/dwarf/line/line_parser_test.go
+++ b/pkg/dwarf/line/line_parser_test.go
@@ -74,7 +74,7 @@ func ptrSizeByRuntimeArch() int {
 
 func testDebugLinePrologueParser(p string, t *testing.T) {
 	data := grabDebugLineSection(p, t)
-	debugLines := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
+	debugLines := ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch())
 	mainFileFound := false
 
 	for _, dbl := range debugLines {
@@ -181,7 +181,7 @@ func BenchmarkLineParser(b *testing.B) {
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_ = ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
+		_ = ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch())
 	}
 }
 
@@ -196,7 +196,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
 		tb.Fatal("Could not read test data", err)
 	}
 
-	return ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
+	return ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch())
 }
 
 func BenchmarkStateMachine(b *testing.B) {
@@ -316,7 +316,7 @@ func TestDebugLineC(t *testing.T) {
 		t.Fatal("Could not read test data", err)
 	}
 
-	parsed := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
+	parsed := ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch())
 
 	if len(parsed) == 0 {
 		t.Fatal("Parser result is empty")
@@ -365,7 +365,7 @@ func TestDebugLineDwarf4(t *testing.T) {
 		t.Fatal("Could not read test data", err)
 	}
 
-	debugLines := ParseAll(data, nil, 0, true, 8)
+	debugLines := ParseAll(data, nil, nil, 0, true, 8)
 
 	for _, dbl := range debugLines {
 		if dbl.Prologue.Version == 4 {
diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go
index 24e62762..b7281151 100644
--- a/pkg/dwarf/line/state_machine_test.go
+++ b/pkg/dwarf/line/state_machine_test.go
@@ -74,7 +74,7 @@ func TestGrafana(t *testing.T) {
 		}
 		cuname, _ := e.Val(dwarf.AttrName).(string)
 
-		lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false, 8)
+		lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, nil, t.Logf, 0, false, 8)
 		lineInfo.endSeqIsValid = true
 		sm := newStateMachine(lineInfo, lineInfo.Instructions, 8)
 
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index d2cafaf1..a455a445 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -2742,104 +2742,6 @@ func getg(goid int, gs []*proc.G) *proc.G {
 	return nil
 }
 
-func TestStacktraceWithBarriers(t *testing.T) {
-	// Go's Garbage Collector will insert stack barriers into stacks.
-	// This stack barrier is inserted by overwriting the return address for the
-	// stack frame with the address of runtime.stackBarrier.
-	// The original return address is saved into the stkbar slice inside the G
-	// struct.
-
-	// In Go 1.9 stack barriers have been removed and this test must be disabled.
-	if ver, _ := goversion.Parse(runtime.Version()); ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
-		return
-	}
-
-	// In Go 1.8 stack barriers are not inserted by default, this enables them.
-	godebugOld := os.Getenv("GODEBUG")
-	defer os.Setenv("GODEBUG", godebugOld)
-	os.Setenv("GODEBUG", "gcrescanstacks=1")
-
-	withTestProcess("binarytrees", t, func(p *proc.Target, fixture protest.Fixture) {
-		// We want to get a user goroutine with a stack barrier, to get that we execute the program until runtime.gcInstallStackBarrier is executed AND the goroutine it was executed onto contains a call to main.bottomUpTree
-		setFunctionBreakpoint(p, t, "runtime.gcInstallStackBarrier")
-		stackBarrierGoids := []int{}
-		for len(stackBarrierGoids) == 0 {
-			err := p.Continue()
-			if _, exited := err.(proc.ErrProcessExited); exited {
-				t.Logf("Could not run test")
-				return
-			}
-			assertNoError(err, t, "Continue()")
-			gs, _, err := proc.GoroutinesInfo(p, 0, 0)
-			assertNoError(err, t, "GoroutinesInfo()")
-			for _, th := range p.ThreadList() {
-				if bp := th.Breakpoint(); bp.Breakpoint == nil {
-					continue
-				}
-
-				goidVar := evalVariable(p, t, "gp.goid")
-				goid, _ := constant.Int64Val(goidVar.Value)
-
-				if g := getg(int(goid), gs); g != nil {
-					stack, err := g.Stacktrace(50, 0)
-					assertNoError(err, t, fmt.Sprintf("Stacktrace(goroutine = %d)", goid))
-					for _, frame := range stack {
-						if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" {
-							stackBarrierGoids = append(stackBarrierGoids, int(goid))
-							break
-						}
-					}
-				}
-			}
-		}
-
-		if len(stackBarrierGoids) == 0 {
-			t.Fatalf("Could not find a goroutine with stack barriers")
-		}
-
-		t.Logf("stack barrier goids: %v\n", stackBarrierGoids)
-
-		assertNoError(p.StepOut(), t, "StepOut()")
-
-		gs, _, err := proc.GoroutinesInfo(p, 0, 0)
-		assertNoError(err, t, "GoroutinesInfo()")
-
-		for _, goid := range stackBarrierGoids {
-			g := getg(goid, gs)
-
-			stack, err := g.Stacktrace(200, 0)
-			assertNoError(err, t, "Stacktrace()")
-
-			// Check that either main.main or main.main.func1 appear in the
-			// stacktrace of this goroutine, if we failed at resolving stack barriers
-			// correctly the stacktrace will be truncated and neither main.main or
-			// main.main.func1 will appear
-			found := false
-			for _, frame := range stack {
-				if frame.Current.Fn == nil {
-					continue
-				}
-				if name := frame.Current.Fn.Name; name == "main.main" || name == "main.main.func1" {
-					found = true
-				}
-			}
-
-			t.Logf("Stacktrace for %d:\n", goid)
-			for _, frame := range stack {
-				name := "<>"
-				if frame.Current.Fn != nil {
-					name = frame.Current.Fn.Name
-				}
-				t.Logf("\t%s [CFA: %x Ret: %x] at %s:%d", name, frame.Regs.CFA, frame.Ret, frame.Current.File, frame.Current.Line)
-			}
-
-			if !found {
-				t.Logf("Truncated stacktrace for %d\n", goid)
-			}
-		}
-	})
-}
-
 func TestAttachDetach(t *testing.T) {
 	if testBackend == "lldb" && runtime.GOOS == "linux" {
 		bs, _ := ioutil.ReadFile("/proc/sys/kernel/yama/ptrace_scope")
@@ -3298,7 +3200,7 @@ func stacktraceCheck(t *testing.T, tc []string, frames []proc.Stackframe) []int
 
 func frameInFile(frame proc.Stackframe, file string) bool {
 	for _, loc := range []proc.Location{frame.Current, frame.Call} {
-		if !strings.HasSuffix(loc.File, "/"+file) && !strings.HasSuffix(loc.File, "\\"+file) {
+		if !strings.HasSuffix(loc.File, file) && !strings.HasSuffix(loc.File, "/"+file) && !strings.HasSuffix(loc.File, "\\"+file) {
 			return false
 		}
 		if loc.Line <= 0 {
@@ -3436,7 +3338,7 @@ func TestCgoSources(t *testing.T) {
 		for _, needle := range []string{"main.go", "hello.c"} {
 			found := false
 			for _, k := range sources {
-				if strings.HasSuffix(k, "/"+needle) || strings.HasSuffix(k, "\\"+needle) {
+				if strings.HasSuffix(k, needle) || strings.HasSuffix(k, "/"+needle) || strings.HasSuffix(k, "\\"+needle) {
 					found = true
 					break
 				}
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index e622c029..cd985f7c 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -943,6 +943,7 @@ func TestOptimizationCheck(t *testing.T) {
 }
 
 func TestTruncateStacktrace(t *testing.T) {
+	const stacktraceTruncatedMessage = "(truncated)"
 	withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) {
 		term.MustExec("break main.stacktraceme")
 		term.MustExec("continue")
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index 8419f1c5..3ad61ae1 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -343,6 +343,24 @@ func (c *Client) VariablesRequest(variablesReference int) {
 	c.send(request)
 }
 
+// IndexedVariablesRequest sends a 'variables' request.
+func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) {
+	request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
+	request.Arguments.VariablesReference = variablesReference
+	request.Arguments.Filter = "indexed"
+	request.Arguments.Start = start
+	request.Arguments.Count = count
+	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 e89ae6ed..220685d0 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -368,6 +368,11 @@ func TestAttachStopOnEntry(t *testing.T) {
 		// 13 >> disconnect, << disconnect
 		client.DisconnectRequestWithKillOption(true)
 
+		// Disconnect consists of Halt + Detach.
+		// Halt interrupts command in progress, which triggers
+		// a stopped event in parallel with the disconnect
+		// sequence. It might arrive before or during the sequence
+		// or never if the server exits before it is sent.
 		msg := expectMessageFilterStopped(t, client)
 		client.CheckOutputEvent(t, msg)
 		msg = expectMessageFilterStopped(t, client)
@@ -511,7 +516,7 @@ func TestPreSetBreakpoint(t *testing.T) {
 		wantMain := dap.Thread{Id: 1, Name: "* [Go 1] main.Increment (Thread ...)"}
 		wantRuntime := dap.Thread{Id: 2, Name: "[Go 2] runtime.gopark"}
 		for _, got := range tResp.Body.Threads {
-			if got.Id != 1 && !reMain.MatchString(got.Name) && !strings.Contains(got.Name, "runtime") {
+			if got.Id != 1 && !reMain.MatchString(got.Name) && !strings.Contains(got.Name, "runtime.") {
 				t.Errorf("\ngot  %#v\nwant []dap.Thread{%#v, %#v, ...}", tResp.Body.Threads, wantMain, wantRuntime)
 			}
 		}
@@ -662,7 +667,7 @@ func checkChildren(t *testing.T, got *dap.VariablesResponse, parentName string,
 //     useExactMatch - true if name, evalName and value are to be compared to exactly, false if to be used as regex
 //     hasRef - true if the variable should have children and therefore a non-0 variable reference
 //     ref - reference to retrieve children of this variable (0 if none)
-func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, useExactMatch, hasRef bool) (ref int) {
+func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, useExactMatch, hasRef bool, indexed, named int) (ref int) {
 	t.Helper()
 	if len(got.Body.Variables) <= i {
 		t.Errorf("\ngot  len=%d (children=%#v)\nwant len>%d", len(got.Body.Variables), got.Body.Variables, i)
@@ -677,7 +682,7 @@ func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, v
 		}
 	}
 	if i < 0 {
-		t.Errorf("\ngot  %#v\nwant Variables[i].Name=%q", got, name)
+		t.Errorf("\ngot  %#v\nwant Variables[i].Name=%q (not found)", got, name)
 		return 0
 	}
 
@@ -718,19 +723,37 @@ func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, v
 	if !matchedType {
 		t.Errorf("\ngot  %s=%q\nwant %q", name, goti.Type, typ)
 	}
+	if indexed >= 0 && goti.IndexedVariables != indexed {
+		t.Errorf("\ngot  %s=%d indexed\nwant %d indexed", name, goti.IndexedVariables, indexed)
+	}
+	if named >= 0 && goti.NamedVariables != named {
+		t.Errorf("\ngot  %s=%d named\nwant %d named", name, goti.NamedVariables, named)
+	}
 	return goti.VariablesReference
 }
 
 // checkVarExact is a helper like checkVar that matches value exactly.
 func checkVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
 	t.Helper()
-	return checkVar(t, got, i, name, evalName, value, typ, true, hasRef)
+	return checkVarExactIndexed(t, got, i, name, evalName, value, typ, hasRef, -1, -1)
+}
+
+// checkVarExact is a helper like checkVar that matches value exactly.
+func checkVarExactIndexed(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool, indexed, named int) (ref int) {
+	t.Helper()
+	return checkVar(t, got, i, name, evalName, value, typ, true, hasRef, indexed, named)
 }
 
 // checkVarRegex is a helper like checkVar that treats value, evalName or name as a regex.
 func checkVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
 	t.Helper()
-	return checkVar(t, got, i, name, evalName, value, typ, false, hasRef)
+	return checkVarRegexIndexed(t, got, i, name, evalName, value, typ, hasRef, -1, -1)
+}
+
+// checkVarRegex is a helper like checkVar that treats value, evalName or name as a regex.
+func checkVarRegexIndexed(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool, indexed, named int) (ref int) {
+	t.Helper()
+	return checkVar(t, got, i, name, evalName, value, typ, false, hasRef, indexed, named)
 }
 
 func expectMessageFilterStopped(t *testing.T, client *daptest.Client) dap.Message {
@@ -1299,14 +1322,6 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 							validateEvaluateName(t, client, m3kv1, 1)
 						}
 					}
-					// key - compound + truncated, value - scalar
-					ref = checkVarExact(t, locals, -1, "m5", "m5", `map[main.C]int [{s: "very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"}: 1, ]`, "map[main.C]int", hasChildren)
-					if ref > 0 {
-						client.VariablesRequest(ref)
-						m5 := client.ExpectVariablesResponse(t)
-						checkChildren(t, m5, "m5", 1)
-						checkVarRegex(t, m5, 0, `main\.C {s: "very long string 0123456789a0123456789b0123456789c01\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren)
-					}
 					// key - compound, value - compound
 					ref = checkVarExact(t, locals, -1, "m4", "m4", "map[main.astruct]main.astruct [{A: 1, B: 1}: {A: 11, B: 11}, {A: 2, B: 2}: {A: 22, B: 22}, ]", "map[main.astruct]main.astruct", hasChildren)
 					if ref > 0 {
@@ -1364,7 +1379,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 					checkVarExact(t, locals, -1, "emptyslice", "emptyslice", "[]string len: 0, cap: 0, []", "[]string", noChildren)
 					checkVarExact(t, locals, -1, "nilslice", "nilslice", "[]int len: 0, cap: 0, nil", "[]int", noChildren)
 					// reflect.Kind == String
-					checkVarExact(t, locals, -1, "longstr", "longstr", `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`, "string", noChildren)
+					checkVarExact(t, locals, -1, "longstr", "longstr", longstr, "string", noChildren)
 					// reflect.Kind == Struct
 					checkVarExact(t, locals, -1, "zsvar", "zsvar", "struct {} {}", "struct {}", noChildren)
 					// reflect.Kind == UnsafePointer
@@ -1461,8 +1476,12 @@ func TestVariablesLoading(t *testing.T) {
 			}, {
 				execute: func() {
 					// Change default config values to trigger certain unloaded corner cases
+					saveDefaultConfig := DefaultLoadConfig
 					DefaultLoadConfig.MaxStructFields = 5
-					defer func() { DefaultLoadConfig.MaxStructFields = -1 }()
+					DefaultLoadConfig.MaxStringLen = 64
+					defer func() {
+						DefaultLoadConfig = saveDefaultConfig
+					}()
 
 					client.StackTraceRequest(1, 0, 0)
 					client.ExpectStackTraceResponse(t)
@@ -1474,30 +1493,93 @@ func TestVariablesLoading(t *testing.T) {
 					locals := client.ExpectVariablesResponse(t)
 
 					// String partially missing based on LoadConfig.MaxStringLen
-					checkVarExact(t, locals, -1, "longstr", "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", "string", noChildren)
+					// See also TestVariableLoadingOfLongStrings
+					checkVarExact(t, locals, -1, "longstr", "longstr", longstrLoaded64, "string", noChildren)
+
+					checkArrayChildren := func(t *testing.T, longarr *dap.VariablesResponse, parentName string, start int) {
+						t.Helper()
+						for i, child := range longarr.Body.Variables {
+							idx := start + i
+							if child.Name != fmt.Sprintf("[%d]", idx) || child.EvaluateName != fmt.Sprintf("%s[%d]", parentName, idx) {
+								t.Errorf("Expected %s[%d] to have Name=\"[%d]\" EvaluateName=\"%s[%d]\", got %#v", parentName, idx, idx, parentName, idx, child)
+							}
+						}
+					}
 
-					// Array partially missing based on LoadConfig.MaxArrayValues
-					ref := checkVarExact(t, locals, -1, "longarr", "longarr", "(loaded 64/100) [100]int [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[100]int", hasChildren)
+					// Array not fully loaded based on LoadConfig.MaxArrayValues.
+					// Expect to be able to load array by paging.
+					ref := checkVarExactIndexed(t, locals, -1, "longarr", "longarr", "[100]int [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[100]int", hasChildren, 100, 0)
 					if ref > 0 {
 						client.VariablesRequest(ref)
 						longarr := client.ExpectVariablesResponse(t)
 						checkChildren(t, longarr, "longarr", 64)
+						checkArrayChildren(t, longarr, "longarr", 0)
+
+						client.IndexedVariablesRequest(ref, 0, 100)
+						longarr = client.ExpectVariablesResponse(t)
+						checkChildren(t, longarr, "longarr", 100)
+						checkArrayChildren(t, longarr, "longarr", 0)
+
+						client.IndexedVariablesRequest(ref, 50, 50)
+						longarr = client.ExpectVariablesResponse(t)
+						checkChildren(t, longarr, "longarr", 50)
+						checkArrayChildren(t, longarr, "longarr", 50)
 					}
 
-					// Slice partially missing based on LoadConfig.MaxArrayValues
-					ref = checkVarExact(t, locals, -1, "longslice", "longslice", "(loaded 64/100) []int len: 100, cap: 100, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[]int", hasChildren)
+					// Slice not fully loaded based on LoadConfig.MaxArrayValues.
+					// Expect to be able to load slice by paging.
+					ref = checkVarExactIndexed(t, locals, -1, "longslice", "longslice", "[]int len: 100, cap: 100, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+36 more]", "[]int", hasChildren, 100, 0)
 					if ref > 0 {
 						client.VariablesRequest(ref)
 						longarr := client.ExpectVariablesResponse(t)
 						checkChildren(t, longarr, "longslice", 64)
+						checkArrayChildren(t, longarr, "longslice", 0)
+
+						client.IndexedVariablesRequest(ref, 0, 100)
+						longarr = client.ExpectVariablesResponse(t)
+						checkChildren(t, longarr, "longslice", 100)
+						checkArrayChildren(t, longarr, "longslice", 0)
+
+						client.IndexedVariablesRequest(ref, 50, 50)
+						longarr = client.ExpectVariablesResponse(t)
+						checkChildren(t, longarr, "longslice", 50)
+						checkArrayChildren(t, longarr, "longslice", 50)
 					}
 
-					// Map partially missing based on LoadConfig.MaxArrayValues
-					ref = checkVarRegex(t, locals, -1, "m1", "m1", `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
+					// Map not fully loaded based on LoadConfig.MaxArrayValues
+					// Expect to be able to load map by paging.
+					ref = checkVarRegexIndexed(t, locals, -1, "m1", "m1", `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren, 66, 0)
 					if ref > 0 {
 						client.VariablesRequest(ref)
 						m1 := client.ExpectVariablesResponse(t)
 						checkChildren(t, m1, "m1", 64)
+
+						client.IndexedVariablesRequest(ref, 0, 66)
+						m1 = client.ExpectVariablesResponse(t)
+						checkChildren(t, m1, "m1", 66)
+
+						client.IndexedVariablesRequest(ref, 0, 33)
+						m1part1 := client.ExpectVariablesResponse(t)
+						checkChildren(t, m1part1, "m1", 33)
+
+						client.IndexedVariablesRequest(ref, 33, 33)
+						m1part2 := client.ExpectVariablesResponse(t)
+						checkChildren(t, m1part2, "m1", 33)
+
+						if len(m1part1.Body.Variables)+len(m1part2.Body.Variables) == len(m1.Body.Variables) {
+							for i, got := range m1part1.Body.Variables {
+								want := m1.Body.Variables[i]
+								if got.Name != want.Name || got.Value != want.Value {
+									t.Errorf("got %#v, want Name=%q Value=%q", got, want.Name, want.Value)
+								}
+							}
+							for i, got := range m1part2.Body.Variables {
+								want := m1.Body.Variables[i+len(m1part1.Body.Variables)]
+								if got.Name != want.Name || got.Value != want.Value {
+									t.Errorf("got %#v, want Name=%q Value=%q", got, want.Name, want.Value)
+								}
+							}
+						}
 					}
 
 					// Struct partially missing based on LoadConfig.MaxStructFields
@@ -1706,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
@@ -1864,7 +2029,7 @@ func checkBreakpoints(t *testing.T, client *daptest.Client, bps []Breakpoint, br
 	}
 	for i, bp := range breakpoints {
 		if bps[i].line < 0 && !bps[i].verified {
-			if bp.Verified != bps[i].verified || !strings.Contains(bp.Message, bps[i].msgPrefix) {
+			if bp.Verified != bps[i].verified || !stringContainsCaseInsensitive(bp.Message, bps[i].msgPrefix) {
 				t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
 			}
 			continue
@@ -1907,7 +2072,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 					// Set another breakpoint inside the loop in loop(), twice to trigger error
 					client.SetBreakpointsRequest(fixture.Source, []int{8, 8})
-					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {-1, "", false, "Breakpoint exists"}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {-1, "", false, "breakpoint exists"}})
 
 					// Continue into the loop
 					client.ContinueRequest(1)
@@ -1954,6 +2119,10 @@ func TestSetBreakpoint(t *testing.T) {
 	})
 }
 
+func stringContainsCaseInsensitive(got, want string) bool {
+	return strings.Contains(strings.ToLower(got), strings.ToLower(want))
+}
+
 // TestSetFunctionBreakpoints is inspired by service/test.TestClientServer_FindLocations.
 func TestSetFunctionBreakpoints(t *testing.T) {
 	runTest(t, "locationsprog", func(client *daptest.Client, fixture protest.Fixture) {
@@ -1983,7 +2152,7 @@ func TestSetFunctionBreakpoints(t *testing.T) {
 						}
 						for i, bp := range got.Body.Breakpoints {
 							if bps[i].line < 0 && !bps[i].verified {
-								if bp.Verified != bps[i].verified || !strings.Contains(bp.Message, bps[i].errMsg) {
+								if bp.Verified != bps[i].verified || !stringContainsCaseInsensitive(bp.Message, bps[i].errMsg) {
 									t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
 								}
 								continue
@@ -2130,7 +2299,7 @@ func TestSetFunctionBreakpoints(t *testing.T) {
 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 						{Name: "SomeType.String"}, {Name: "(*SomeType).String"},
 					})
-					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}, {-1, "", false, "Breakpoint exists"}})
+					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}, {-1, "", false, "breakpoint exists"}})
 
 					// Set two breakpoints at SomeType.String and SomeType.SomeFunction.
 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
@@ -2565,6 +2734,18 @@ func checkEval(t *testing.T, got *dap.EvaluateResponse, value string, hasRef boo
 	return got.Body.VariablesReference
 }
 
+// checkEvalIndexed is a helper for verifying the values within an EvaluateResponse.
+//     value - the value of the evaluated expression
+//     hasRef - true if the evaluated expression should have children and therefore a non-0 variable reference
+//     ref - reference to retrieve children of this evaluated expression (0 if none)
+func checkEvalIndexed(t *testing.T, got *dap.EvaluateResponse, value string, hasRef bool, indexed, named int) (ref int) {
+	t.Helper()
+	if got.Body.Result != value || (got.Body.VariablesReference > 0) != hasRef || got.Body.IndexedVariables != indexed || got.Body.NamedVariables != named {
+		t.Errorf("\ngot  %#v\nwant Result=%q hasRef=%t IndexedVariables=%d NamedVariables=%d", got, value, hasRef, indexed, named)
+	}
+	return got.Body.VariablesReference
+}
+
 func checkEvalRegex(t *testing.T, got *dap.EvaluateResponse, valueRegex string, hasRef bool) (ref int) {
 	t.Helper()
 	matched, _ := regexp.MatchString(valueRegex, got.Body.Result)
@@ -2607,7 +2788,7 @@ func TestEvaluateRequest(t *testing.T) {
 					// Variable lookup that's not fully loaded
 					client.EvaluateRequest("ba", 1000, "this context will be ignored")
 					got = client.ExpectEvaluateResponse(t)
-					checkEval(t, got, "(loaded 64/200) []int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", hasChildren)
+					checkEvalIndexed(t, got, "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", hasChildren, 200, 0)
 
 					// All (binary and unary) on basic types except <-, ++ and --
 					client.EvaluateRequest("1+1", 1000, "this context will be ignored")
@@ -2639,7 +2820,7 @@ func TestEvaluateRequest(t *testing.T) {
 					// Type casts between string, []byte and []rune
 					client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
 					got = client.ExpectEvaluateResponse(t)
-					checkEval(t, got, "[]uint8 len: 6, cap: 6, [65,66,67,226,130,172]", noChildren)
+					checkEvalIndexed(t, got, "[]uint8 len: 6, cap: 6, [65,66,67,226,130,172]", noChildren, 6, 0)
 
 					// Struct member access (i.e. somevar.memberfield)
 					client.EvaluateRequest("ms.Nest.Level", 1000, "this context will be ignored")
@@ -2734,64 +2915,159 @@ func TestEvaluateRequest(t *testing.T) {
 	})
 }
 
-func TestEvaluateRequestLongStrLargeValue(t *testing.T) {
+// From testvariables2 fixture
+const (
+	// As defined in the code
+	longstr = `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
+	// Loaded with MaxStringLen=64
+	longstrLoaded64   = `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
+	longstrLoaded64re = `\"very long string 0123456789a0123456789b0123456789c0123456789d012\.\.\.\+73 more\"`
+)
+
+// TestVariableValueTruncation tests that in certain cases
+// we truncate the loaded variable values to make display more user-friendly.
+func TestVariableValueTruncation(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
+			// Breakpoint set within the program
 			fixture.Source, []int{},
 			[]onBreakpoint{{
 				execute: func() {
-
 					checkStop(t, client, 1, "main.main", -1)
 
 					client.VariablesRequest(1001) // Locals
 					locals := client.ExpectVariablesResponse(t)
 
-					// reflect.Kind == String, load with longer load limit if evaluated in repl/variables context.
-					for _, evalContext := range []string{"", "watch", "repl", "variables", "hover", "clipboard", "somethingelse"} {
-						t.Run(evalContext, func(t *testing.T) {
-							// long string
+					// Compound variable values may be truncated
+					m1Full := `map\[string\]main\.astruct \[(\"[A-Za-z]+\": {A: [0-9]+, B: [0-9]+}, )+,\.\.\.\+2 more\]`
+					m1Part := `map\[string\]main\.astruct \[(\"[A-Za-z]+\": {A: [0-9]+, B: [0-9]+}, )+.+\.\.\.`
 
-							longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
-							longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
+					// In variable responses
+					checkVarRegex(t, locals, -1, "m1", "m1", m1Part, `map\[string\]main\.astruct`, hasChildren)
 
-							client.EvaluateRequest("longstr", 0, evalContext)
-							got := client.ExpectEvaluateResponse(t)
-							want := longstrTruncated
-							switch evalContext {
-							case "repl", "variables", "hover", "clipboard":
-								want = longstr
-							}
-							checkEval(t, got, want, false)
-
-							// long string as a struct field
-							client.EvaluateRequest("(m6).s", 0, evalContext)
-							got2 := client.ExpectEvaluateResponse(t)
-							checkEval(t, got2, want, false)
-
-							// variables are not affected.
-							checkVarExact(t, locals, -1, "longstr", "longstr", longstrTruncated, "string", noChildren)
-							checkVarExact(t, locals, -1, "m6", "m6", `main.C {s: `+longstrTruncated+`}`, "main.C", hasChildren)
-
-							// large array
-							m1 := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`
-							m1Truncated := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`
-
-							client.EvaluateRequest("m1", 0, evalContext)
-							got3 := client.ExpectEvaluateResponse(t)
-							want3 := m1Truncated
-							switch evalContext {
-							case "variables", "hover", "clipboard":
-								want3 = m1
+					// In evaluate responses (select contexts only)
+					tests := []struct {
+						context string
+						want    string
+					}{
+						{"", m1Part},
+						{"watch", m1Part},
+						{"repl", m1Part},
+						{"hover", m1Part},
+						{"variables", m1Full}, // used for copy
+						{"clipboard", m1Full}, // used for copy
+						{"somethingelse", m1Part},
+					}
+					for _, tc := range tests {
+						t.Run(tc.context, func(t *testing.T) {
+							client.EvaluateRequest("m1", 0, tc.context)
+							checkEvalRegex(t, client.ExpectEvaluateResponse(t), tc.want, hasChildren)
+						})
+					}
+
+					// Compound map keys may be truncated even further
+					// As the keys are always inside of a map container,
+					// this applies to variables requests only, not evalute requests.
+
+					// key - compound, value - scalar (inlined key:value display) => truncate key if too long
+					ref := checkVarExact(t, locals, -1, "m5", "m5", "map[main.C]int [{s: "+longstr+"}: 1, ]", "map[main.C]int", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						// Key format: <truncated>... @<address>
+						checkVarRegex(t, client.ExpectVariablesResponse(t), 0, `main\.C {s: "very long string 0123456789.+\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren)
+					}
+					// key - scalar, value - scalar (inlined key:value display) => key not truncated
+					ref = checkVarExact(t, locals, -1, "m6", "m6", "map[string]int ["+longstr+": 123, ]", "map[string]int", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						checkVarExact(t, client.ExpectVariablesResponse(t), 0, longstr, `m6[`+longstr+`]`, "123", "string: int", noChildren)
+					}
+					// key - compound, value - compound (array-like display) => key not truncated
+					ref = checkVarExact(t, locals, -1, "m7", "m7", "map[main.C]main.C [{s: "+longstr+"}: {s: \"hello\"}, ]", "map[main.C]main.C", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						m7 := client.ExpectVariablesResponse(t)
+						checkVarRegex(t, m7, 0, "[key 0]", `\(\*\(\*\"main\.C\"\)\(0x[0-9a-f]+\)\)`, `main\.C {s: `+longstr+`}`, `main\.C`, hasChildren)
+					}
+				},
+				disconnect: true,
+			}})
+	})
+}
+
+// TestVariableLoadingOfLongStrings tests that different string loading limits
+// apply that depending on the context.
+func TestVariableLoadingOfLongStrings(t *testing.T) {
+	protest.MustSupportFunctionCalls(t, testBackend)
+	runTest(t, "longstrings", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Breakpoint set within the program
+			fixture.Source, []int{},
+			[]onBreakpoint{{
+				execute: func() {
+					checkStop(t, client, 1, "main.main", -1)
+
+					client.VariablesRequest(1001) // Locals
+					locals := client.ExpectVariablesResponse(t)
+
+					// Limits vary for evaluate requests with different contexts
+					tests := []struct {
+						context string
+						limit   int
+					}{
+						{"", DefaultLoadConfig.MaxStringLen},
+						{"watch", DefaultLoadConfig.MaxStringLen},
+						{"repl", maxSingleStringLen},
+						{"hover", maxSingleStringLen},
+						{"variables", maxSingleStringLen},
+						{"clipboard", maxSingleStringLen},
+						{"somethingelse", DefaultLoadConfig.MaxStringLen},
+					}
+					for _, tc := range tests {
+						t.Run(tc.context, func(t *testing.T) {
+							// Long string by itself (limits vary)
+							client.EvaluateRequest("s4097", 0, tc.context)
+							want := fmt.Sprintf(`"x+\.\.\.\+%d more"`, 4097-tc.limit)
+							checkEvalRegex(t, client.ExpectEvaluateResponse(t), want, noChildren)
+
+							// Evaluated container variables return values with minimally loaded
+							// strings, which are further truncated for displaying, so we
+							// can't test for loading limit except in contexts where an untruncated
+							// value is returned.
+							client.EvaluateRequest("&s4097", 0, tc.context)
+							switch tc.context {
+							case "variables", "clipboard":
+								want = fmt.Sprintf(`\*"x+\.\.\.\+%d more`, 4097-DefaultLoadConfig.MaxStringLen)
+							default:
+								want = fmt.Sprintf(`\*"x{%d}\.\.\.`, maxVarValueLen-2)
 							}
-							checkEvalRegex(t, got3, want3, true)
-							checkVarRegex(t, locals, -1, "m1", "m1", m1Truncated, `map\[string\]main\.astruct`, true)
+							checkEvalRegex(t, client.ExpectEvaluateResponse(t), want, hasChildren)
+						})
+					}
+
+					// Long strings returned from calls are subject to a different limit,
+					// same limit regardless of context
+					for _, context := range []string{"", "watch", "repl", "variables", "hover", "clipboard", "somethingelse"} {
+						t.Run(context, func(t *testing.T) {
+							client.EvaluateRequest(`call buildString(4097)`, 1000, context)
+							want := fmt.Sprintf(`"x+\.\.\.\+%d more"`, 4097-maxStringLenInCallRetVars)
+							got := client.ExpectEvaluateResponse(t)
+							checkEvalRegex(t, got, want, hasChildren)
 						})
 					}
+
+					// Variables requests use the most conservative loading limit
+					checkVarRegex(t, locals, -1, "s513", "s513", `"x{512}\.\.\.\+1 more"`, "string", noChildren)
+					// Container variables are subject to additional stricter value truncation that drops +more part
+					checkVarRegex(t, locals, -1, "nested", "nested", `map\[int\]string \[513: \"x+\.\.\.`, "string", hasChildren)
 				},
 				disconnect: true,
 			}})
@@ -2928,13 +3204,6 @@ func TestEvaluateCallRequest(t *testing.T) {
 					client.EvaluateRequest("call ", 1000, "watch")
 					got = client.ExpectEvaluateResponse(t)
 					checkEval(t, got, "\"this is a variable named `call`\"", noChildren)
-					// Long string as a return value
-					client.EvaluateRequest(`call stringsJoin(longstrs, ",")`, 1000, "variables") // full string
-					got = client.ExpectEvaluateResponse(t)
-					checkEval(t, got, `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`, hasChildren)
-					client.EvaluateRequest(`call stringsJoin(longstrs, ",")`, 1000, "watch") // full string
-					got = client.ExpectEvaluateResponse(t)
-					checkEval(t, got, `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`, hasChildren)
 
 					// Call error
 					client.EvaluateRequest("call call1(one)", 1000, "watch")
@@ -3221,7 +3490,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)
@@ -3275,6 +3543,19 @@ func TestPanicBreakpointOnContinue(t *testing.T) {
 					if eInfo.Body.ExceptionId != "panic" || eInfo.Body.Description != "\"BOOM!\"" {
 						t.Errorf("\ngot  %#v\nwant ExceptionId=\"panic\" Description=\"\"BOOM!\"\"", eInfo)
 					}
+
+					client.StackTraceRequest(se.Body.ThreadId, 0, 20)
+					st := client.ExpectStackTraceResponse(t)
+					for i, frame := range st.Body.StackFrames {
+						if strings.HasPrefix(frame.Name, "runtime.") {
+							if frame.Source.PresentationHint != "deemphasize" {
+								t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"deemphasize\"", i, frame)
+							}
+						} else if frame.Source.PresentationHint != "" {
+							t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"\"", i, frame)
+						}
+
+					}
 				},
 				disconnect: true,
 			}})
@@ -3320,6 +3601,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
@@ -3339,6 +3655,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,
 			}})
@@ -3505,7 +3825,7 @@ func TestLaunchDebugRequest(t *testing.T) {
 		// BinaryInfo.Close(), but it appears that it is still in use (by Windows?)
 		// shortly after. gobuild.Remove has a delay to address this, but
 		// to avoid any test flakiness we guard against this failure here as well.
-		if runtime.GOOS != "windows" || !strings.Contains(rmErr, "Access is denied") {
+		if runtime.GOOS != "windows" || !stringContainsCaseInsensitive(rmErr, "Access is denied") {
 			t.Fatalf("Binary removal failure:\n%s\n", rmErr)
 		}
 	} else {
@@ -3818,7 +4138,7 @@ func (h *helperForSetVariable) failSetVariable0(ref int, name, value, wantErrInf
 		h.c.ExpectStoppedEvent(h.t)
 	}
 	resp := h.c.ExpectErrorResponse(h.t)
-	if got := resp.Body.Error.Format; !strings.Contains(got, wantErrInfo) {
+	if got := resp.Body.Error.Format; !stringContainsCaseInsensitive(got, wantErrInfo) {
 		h.t.Errorf("got %#v, want error string containing %v", got, wantErrInfo)
 	}
 }
@@ -3947,7 +4267,7 @@ func TestSetVariable(t *testing.T) {
 				execute: func() {
 					tester := &helperForSetVariable{t, client}
 
-					startLineno := 358 // after runtime.Breakpoint
+					startLineno := 360 // after runtime.Breakpoint
 					if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 						startLineno = -1
 					}
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 9388a869..a59e13df 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1773,6 +1773,23 @@ func TestClientServerConsistentExit(t *testing.T) {
 		if state.ExitStatus != 2 {
 			t.Fatalf("Process exit status is not 2, got: %v", state.ExitStatus)
 		}
+
+		// Ensure future commands also return the correct exit status.
+		// Previously there was a bug where the command which prompted the
+		// process to exit (continue, next, etc...) would return the corrent
+		// exit status but subsequent commands would return an incorrect exit
+		// status of 0. To test this we simply repeat the 'next' command and
+		// ensure we get the correct response again.
+		state, err = c.Next()
+		if err != nil {
+			t.Fatalf("Unexpected error: %v", err)
+		}
+		if !state.Exited {
+			t.Fatal("Second process state is not exited")
+		}
+		if state.ExitStatus != 2 {
+			t.Fatalf("Second process exit status is not 2, got: %v", state.ExitStatus)
+		}
 	})
 }