Repository: go-delve/delve
Pull request #2516 (2021-05-27 19:53:58)

gdbserial: actually fix ZMM register support
PR #2498 / commit d552b338 left out some code due to a clerical error.


aarzilli: cc @vsivsi

derekparker: > PR #2498 / commit [d552b33](https://github.com/go-delve/delve/commit/d552b33822c7d913110789a4a4ca282a4c5c93c6) left out some code due to a clerical error.

😆 
derekparker: I'll wait for @vsivsi to ack the fix.

Patch:
diff --git a/.gitignore b/.gitignore
index b117424d..f6073ada 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ localtests
 .idea
 *.iml
 .teamcity/target
+.vscode
diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md
index 94e7081c..cf1984e3 100644
--- a/Documentation/cli/README.md
+++ b/Documentation/cli/README.md
@@ -174,8 +174,21 @@ If called with the linespec argument it will delete all the breakpoints matching
 Set breakpoint condition.
 
 	condition <breakpoint name or id> <boolean expression>.
+	condition -hitcount <breakpoint name or id> <operator> <argument>
 
-Specifies that the breakpoint or tracepoint should break only if the boolean expression is true.
+Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true.
+
+With the -hitcount option a condition on the breakpoint hit count can be set, the following operators are supported
+
+	condition -hitcount bp > n
+	condition -hitcount bp >= n
+	condition -hitcount bp < n
+	condition -hitcount bp <= n
+	condition -hitcount bp == n
+	condition -hitcount bp != n
+	condition -hitcount bp % n
+	
+The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n.
 
 Aliases: cond
 
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/breakpoints.go b/pkg/proc/breakpoints.go
index 0b5dafea..3c264c0c 100644
--- a/pkg/proc/breakpoints.go
+++ b/pkg/proc/breakpoints.go
@@ -6,6 +6,7 @@ import (
 	"go/ast"
 	"go/constant"
 	"go/parser"
+	"go/token"
 	"reflect"
 )
 
@@ -71,6 +72,12 @@ type Breakpoint struct {
 	Cond ast.Expr
 	// internalCond is the same as Cond but used for the condition of internal breakpoints
 	internalCond ast.Expr
+	// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
+	// true with the TotalHitCount.
+	HitCond *struct {
+		Op  token.Token
+		Val int
+	}
 
 	// ReturnInfo describes how to collect return variables when this
 	// breakpoint is hit as a return breakpoint.
@@ -164,36 +171,72 @@ type returnBreakpointInfo struct {
 // CheckCondition evaluates bp's condition on thread.
 func (bp *Breakpoint) CheckCondition(thread Thread) BreakpointState {
 	bpstate := BreakpointState{Breakpoint: bp, Active: false, Internal: false, CondError: nil}
-	if bp.Cond == nil && bp.internalCond == nil {
+	bpstate.checkCond(thread)
+	// Update the breakpoint hit counts.
+	if bpstate.Breakpoint != nil && bpstate.Active {
+		if g, err := GetG(thread); err == nil {
+			bpstate.HitCount[g.ID]++
+		}
+		bpstate.TotalHitCount++
+	}
+	bpstate.checkHitCond(thread)
+	return bpstate
+}
+
+func (bpstate *BreakpointState) checkCond(thread Thread) {
+	if bpstate.Cond == nil && bpstate.internalCond == nil {
 		bpstate.Active = true
-		bpstate.Internal = bp.IsInternal()
-		return bpstate
+		bpstate.Internal = bpstate.IsInternal()
+		return
 	}
 	nextDeferOk := true
-	if bp.Kind&NextDeferBreakpoint != 0 {
+	if bpstate.Kind&NextDeferBreakpoint != 0 {
 		var err error
 		frames, err := ThreadStacktrace(thread, 2)
 		if err == nil {
 			nextDeferOk = isPanicCall(frames)
 			if !nextDeferOk {
-				nextDeferOk, _ = isDeferReturnCall(frames, bp.DeferReturns)
+				nextDeferOk, _ = isDeferReturnCall(frames, bpstate.DeferReturns)
 			}
 		}
 	}
-	if bp.IsInternal() {
+	if bpstate.IsInternal() {
 		// Check internalCondition if this is also an internal breakpoint
-		bpstate.Active, bpstate.CondError = evalBreakpointCondition(thread, bp.internalCond)
+		bpstate.Active, bpstate.CondError = evalBreakpointCondition(thread, bpstate.internalCond)
 		bpstate.Active = bpstate.Active && nextDeferOk
 		if bpstate.Active || bpstate.CondError != nil {
 			bpstate.Internal = true
-			return bpstate
+			return
 		}
 	}
-	if bp.IsUser() {
+	if bpstate.IsUser() {
 		// Check normal condition if this is also a user breakpoint
-		bpstate.Active, bpstate.CondError = evalBreakpointCondition(thread, bp.Cond)
+		bpstate.Active, bpstate.CondError = evalBreakpointCondition(thread, bpstate.Cond)
+	}
+}
+
+// checkHitCond evaluates bp's hit condition on thread.
+func (bpstate *BreakpointState) checkHitCond(thread Thread) {
+	if bpstate.HitCond == nil || !bpstate.Active || bpstate.Internal {
+		return
+	}
+	// Evaluate the breakpoint condition.
+	switch bpstate.HitCond.Op {
+	case token.EQL:
+		bpstate.Active = int(bpstate.TotalHitCount) == bpstate.HitCond.Val
+	case token.NEQ:
+		bpstate.Active = int(bpstate.TotalHitCount) != bpstate.HitCond.Val
+	case token.GTR:
+		bpstate.Active = int(bpstate.TotalHitCount) > bpstate.HitCond.Val
+	case token.LSS:
+		bpstate.Active = int(bpstate.TotalHitCount) < bpstate.HitCond.Val
+	case token.GEQ:
+		bpstate.Active = int(bpstate.TotalHitCount) >= bpstate.HitCond.Val
+	case token.LEQ:
+		bpstate.Active = int(bpstate.TotalHitCount) <= bpstate.HitCond.Val
+	case token.REM:
+		bpstate.Active = int(bpstate.TotalHitCount)%bpstate.HitCond.Val == 0
 	}
-	return bpstate
 }
 
 func isPanicCall(frames []Stackframe) bool {
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/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index e324b034..d2312c15 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -1787,12 +1787,6 @@ func (t *gdbThread) SetCurrentBreakpoint(adjustPC bool) error {
 			}
 		}
 		t.CurrentBreakpoint = bp.CheckCondition(t)
-		if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
-			if g, err := proc.GetG(t); err == nil {
-				t.CurrentBreakpoint.HitCount[g.ID]++
-			}
-			t.CurrentBreakpoint.TotalHitCount++
-		}
 	}
 	return nil
 }
@@ -1859,8 +1853,11 @@ func (t *gdbThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
 	gdbreg, ok := t.regs.regs[regName]
 	if !ok && strings.HasPrefix(regName, "xmm") {
 		// XMMn and YMMn are the same amd64 register (in different sizes), if we
-		// don't find XMMn try YMMn instead.
+		// don't find XMMn try YMMn or ZMMn instead.
 		gdbreg, ok = t.regs.regs["y"+regName[1:]]
+		if !ok {
+			gdbreg, ok = t.regs.regs["z"+regName[1:]]
+		}
 	}
 	if !ok {
 		return fmt.Errorf("could not set register %s: not found", regName)
@@ -1919,8 +1916,16 @@ func (regs *gdbRegisters) Slice(floatingPoint bool) ([]proc.Register, error) {
 
 			value := regs.regs[reginfo.Name].value
 			xmmName := "x" + reginfo.Name[1:]
-			r = proc.AppendBytesRegister(r, strings.ToUpper(xmmName), value[:16])
-			r = proc.AppendBytesRegister(r, strings.ToUpper(reginfo.Name), value[16:])
+			r = proc.AppendBytesRegister(r, strings.ToUpper(xmmName), value)
+
+		case reginfo.Bitsize == 512:
+			if !strings.HasPrefix(strings.ToLower(reginfo.Name), "zmm") || !floatingPoint {
+				continue
+			}
+
+			value := regs.regs[reginfo.Name].value
+			xmmName := "x" + reginfo.Name[1:]
+			r = proc.AppendBytesRegister(r, strings.ToUpper(xmmName), value)
 		}
 	}
 	return r, nil
diff --git a/pkg/proc/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/native/threads.go b/pkg/proc/native/threads.go
index 62aa55ba..5ae8abc0 100644
--- a/pkg/proc/native/threads.go
+++ b/pkg/proc/native/threads.go
@@ -155,12 +155,6 @@ func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error {
 
 	if bp != nil {
 		t.CurrentBreakpoint = bp.CheckCondition(t)
-		if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
-			if g, err := proc.GetG(t); err == nil {
-				t.CurrentBreakpoint.HitCount[g.ID]++
-			}
-			t.CurrentBreakpoint.TotalHitCount++
-		}
 	}
 	return nil
 }
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 2926dc67..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
@@ -2318,6 +2282,7 @@ func (v *Variable) registerVariableTypeConv(newtyp string) (*Variable, error) {
 	v.loaded = true
 	v.Kind = reflect.Array
 	v.Len = int64(len(v.Children))
+	v.Base = fakeAddress
 	v.DwarfType = fakeArrayType(uint64(len(v.Children)), &godwarf.VoidType{CommonType: godwarf.CommonType{ByteSize: int64(n)}})
 	v.RealType = v.DwarfType
 	return v, nil
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index b243ee52..8c4e36d3 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -387,8 +387,21 @@ Supported commands: print, stack and goroutine)`},
 		{aliases: []string{"condition", "cond"}, group: breakCmds, cmdFn: conditionCmd, helpMsg: `Set breakpoint condition.
 
 	condition <breakpoint name or id> <boolean expression>.
+	condition -hitcount <breakpoint name or id> <operator> <argument>
 
-Specifies that the breakpoint or tracepoint should break only if the boolean expression is true.`},
+Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true.
+
+With the -hitcount option a condition on the breakpoint hit count can be set, the following operators are supported
+
+	condition -hitcount bp > n
+	condition -hitcount bp >= n
+	condition -hitcount bp < n
+	condition -hitcount bp <= n
+	condition -hitcount bp == n
+	condition -hitcount bp != n
+	condition -hitcount bp % n
+	
+The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n.`},
 		{aliases: []string{"config"}, cmdFn: configureCmd, helpMsg: `Changes configuration parameters.
 
 	config -list
@@ -1295,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
 }
@@ -1533,6 +1546,9 @@ func breakpoints(t *Term, ctx callContext, args string) error {
 		if bp.Cond != "" {
 			attrs = append(attrs, fmt.Sprintf("\tcond %s", bp.Cond))
 		}
+		if bp.HitCond != "" {
+			attrs = append(attrs, fmt.Sprintf("\tcond -hitcount %s", bp.HitCond))
+		}
 		if bp.Stacktrace > 0 {
 			attrs = append(attrs, fmt.Sprintf("\tstack %d", bp.Stacktrace))
 		}
@@ -2315,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) {
@@ -2666,6 +2622,22 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error {
 		return fmt.Errorf("not enough arguments")
 	}
 
+	if args[0] == "-hitcount" {
+		// hitcount breakpoint
+		args = split2PartsBySpace(args[1])
+		if len(args) < 2 {
+			return fmt.Errorf("not enough arguments")
+		}
+		bp, err := getBreakpointByIDOrName(t, args[0])
+		if err != nil {
+			return err
+		}
+
+		bp.HitCond = args[1]
+
+		return t.client.AmendBreakpoint(bp)
+	}
+
 	bp, err := getBreakpointByIDOrName(t, args[0])
 	if err != nil {
 		return err
diff --git a/service/api/conversions.go b/service/api/conversions.go
index 08514b4b..7a2d7179 100644
--- a/service/api/conversions.go
+++ b/service/api/conversions.go
@@ -47,6 +47,9 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
 	var buf bytes.Buffer
 	printer.Fprint(&buf, token.NewFileSet(), bp.Cond)
 	b.Cond = buf.String()
+	if bp.HitCond != nil {
+		b.HitCond = fmt.Sprintf("%s %d", bp.HitCond.Op.String(), bp.HitCond.Val)
+	}
 
 	return b
 }
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 26b165d5..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
@@ -67,6 +69,9 @@ type Breakpoint struct {
 
 	// Breakpoint condition
 	Cond string
+	// Breakpoint hit count condition.
+	// Supported hit count conditions are "NUMBER" and "OP NUMBER".
+	HitCond string
 
 	// Tracepoint flag, signifying this is a tracepoint.
 	Tracepoint bool `json:"continue"`
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 d61ae55e..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,17 +166,30 @@ 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;
@@ -548,7 +561,6 @@ func (s *Server) handleRequest(request dap.Message) {
 		s.onSetBreakpointsRequest(request)
 	case *dap.SetFunctionBreakpointsRequest:
 		// Optional (capability ‘supportsFunctionBreakpoints’)
-		// TODO: implement this request in V1
 		s.onSetFunctionBreakpointsRequest(request)
 	case *dap.SetExceptionBreakpointsRequest:
 		// Optional (capability ‘exceptionBreakpointFilters’)
@@ -689,6 +701,7 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
 	response.Body.SupportsExceptionInfoRequest = true
 	response.Body.SupportsSetVariable = true
 	response.Body.SupportsEvaluateForHovers = true
+	response.Body.SupportsClipboardContext = true
 	// TODO(polina): support these requests in addition to vscode-go feature parity
 	response.Body.SupportsTerminateRequest = false
 	response.Body.SupportsRestartRequest = false
@@ -745,8 +758,9 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 	if mode == "debug" || mode == "test" {
 		output, ok := request.Arguments["output"].(string)
 		if !ok || output == "" {
-			output = cleanExeName(defaultDebugBinary)
+			output = defaultDebugBinary
 		}
+		output = cleanExeName(output)
 		debugbinary, err := filepath.Abs(output)
 		if err != nil {
 			s.sendInternalErrorResponse(request.Seq, err.Error())
@@ -970,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 {
@@ -978,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())
@@ -1029,40 +1046,241 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 
 	// According to the spec we should "set multiple breakpoints for a single source
 	// and clear all previous breakpoints in that source." The simplest way is
-	// to clear all and then set all.
+	// to clear all and then set all. To maintain state (for hit count conditions)
+	// we want to amend existing breakpoints.
 	//
-	// TODO(polina): should we optimize this as follows?
 	// See https://github.com/golang/vscode-go/issues/163 for details.
 	// If a breakpoint:
 	// -- exists and not in request => ClearBreakpoint
 	// -- exists and in request => AmendBreakpoint
 	// -- doesn't exist and in request => SetBreakpoint
 
-	// Clear all existing breakpoints in the file that are not function breakpoints.
-	if err := s.clearMatchingBreakpoints(func(bp *api.Breakpoint) bool { return bp.File == serverPath && bp.Name == "" }); err != nil {
+	// Get all existing breakpoints that match for this source.
+	sourceRequestPrefix := fmt.Sprintf("sourceBp Path=%q ", request.Arguments.Source.Path)
+	existingBps := s.getMatchingBreakpoints(sourceRequestPrefix)
+	bpAdded := make(map[string]struct{}, len(existingBps))
+
+	// Amend existing breakpoints.
+	breakpoints := make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
+	for i, want := range request.Arguments.Breakpoints {
+		reqString := fmt.Sprintf("%s Line=%d Column=%d", sourceRequestPrefix, want.Line, want.Column)
+		var err error
+		got, ok := existingBps[reqString]
+		if !ok {
+			// Skip if the breakpoint does not already exist.
+			// These will be created after deleting existing
+			// breakpoints to avoid conflicts.
+			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)
+		} else {
+			got.Cond = want.Condition
+			got.HitCond = want.HitCondition
+			err = s.debugger.AmendBreakpoint(got)
+			bpAdded[reqString] = struct{}{}
+		}
+
+		updateBreakpointsResponse(breakpoints, i, err, got, clientPath)
+	}
+
+	// Clear existing breakpoints that were not added.
+	err := s.clearBreakpoints(existingBps, bpAdded)
+	if err != nil {
 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", err.Error())
 		return
 	}
 
-	// Set all requested breakpoints.
+	for i, want := range request.Arguments.Breakpoints {
+		reqString := fmt.Sprintf("%s Line=%d Column=%d", sourceRequestPrefix, want.Line, want.Column)
+		if _, ok := existingBps[reqString]; ok {
+			continue
+		}
+
+		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)
+		} else {
+			// Create new breakpoints.
+			got, err = s.debugger.CreateBreakpoint(
+				&api.Breakpoint{File: serverPath, Line: want.Line, Cond: want.Condition, HitCond: want.HitCondition, Name: reqString})
+			bpAdded[reqString] = struct{}{}
+		}
+
+		updateBreakpointsResponse(breakpoints, i, err, got, clientPath)
+	}
 	response := &dap.SetBreakpointsResponse{Response: *newResponse(request.Request)}
-	response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
+	response.Body.Breakpoints = breakpoints
+
+	s.send(response)
+}
+
+func updateBreakpointsResponse(breakpoints []dap.Breakpoint, i int, err error, got *api.Breakpoint, path string) {
+	breakpoints[i].Verified = (err == nil)
+	if err != nil {
+		breakpoints[i].Message = err.Error()
+	} else {
+		breakpoints[i].Id = got.ID
+		breakpoints[i].Line = got.Line
+		breakpoints[i].Source = dap.Source{Name: filepath.Base(path), Path: path}
+	}
+}
+
+// functionBpPrefix is the prefix of bp.Name for every breakpoint bp set
+// in this request.
+const functionBpPrefix = "functionBreakpoint"
+
+func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) {
+	if s.noDebugProcess != nil {
+		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "running in noDebug mode")
+		return
+	}
+
+	// According to the spec, setFunctionBreakpoints "replaces all existing function
+	// breakpoints with new function breakpoints." The simplest way is
+	// to clear all and then set all. To maintain state (for hit count conditions)
+	// we want to amend existing breakpoints.
+	//
+	// See https://github.com/golang/vscode-go/issues/163 for details.
+	// If a breakpoint:
+	// -- exists and not in request => ClearBreakpoint
+	// -- exists and in request => AmendBreakpoint
+	// -- doesn't exist and in request => SetBreakpoint
+
+	// Get all existing function breakpoints.
+	existingBps := s.getMatchingBreakpoints(functionBpPrefix)
+	bpAdded := make(map[string]struct{}, len(existingBps))
+	for _, bp := range existingBps {
+		existingBps[bp.Name] = bp
+	}
+
+	// Amend any existing breakpoints.
+	breakpoints := make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
 	for i, want := range request.Arguments.Breakpoints {
-		got, err := s.debugger.CreateBreakpoint(
-			&api.Breakpoint{File: serverPath, Line: want.Line, Cond: want.Condition})
-		response.Body.Breakpoints[i].Verified = (err == nil)
-		if err != nil {
-			response.Body.Breakpoints[i].Line = want.Line
-			response.Body.Breakpoints[i].Message = err.Error()
+		reqString := fmt.Sprintf("%s Name=%s", functionBpPrefix, want.Name)
+		var err error
+		got, ok := existingBps[reqString]
+		if !ok {
+			// Skip if the breakpoint does not already exist.
+			// These will be created after deleting existing
+			// breakpoints to avoid conflicts.
+			continue
+		}
+		if _, ok := bpAdded[reqString]; ok {
+			err = fmt.Errorf("breakpoint exists at function %q", want.Name)
 		} else {
-			response.Body.Breakpoints[i].Id = got.ID
-			response.Body.Breakpoints[i].Line = got.Line
-			response.Body.Breakpoints[i].Source = dap.Source{Name: request.Arguments.Source.Name, Path: clientPath}
+			got.Cond = want.Condition
+			got.HitCond = want.HitCondition
+			err = s.debugger.AmendBreakpoint(got)
+			bpAdded[reqString] = struct{}{}
+		}
+
+		var clientPath string
+		if got != nil {
+			clientPath = s.toClientPath(got.File)
+		}
+		updateBreakpointsResponse(breakpoints, i, err, got, clientPath)
+	}
+
+	// Clear existing breakpoints that were not added.
+	err := s.clearBreakpoints(existingBps, bpAdded)
+	if err != nil {
+		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", err.Error())
+		return
+	}
+
+	// Create new breakpoints.
+	for i, want := range request.Arguments.Breakpoints {
+		reqString := fmt.Sprintf("%s Name=%s", functionBpPrefix, want.Name)
+		if _, ok := existingBps[reqString]; ok {
+			// Amend existing breakpoints.
+			continue
 		}
+
+		// Set the function breakpoint breakpoint
+		spec, err := locspec.Parse(want.Name)
+		if err != nil {
+			breakpoints[i].Message = err.Error()
+			continue
+		}
+		if loc, ok := spec.(*locspec.NormalLocationSpec); !ok || loc.FuncBase == nil {
+			// Other locations do not make sense in the context of function breakpoints.
+			// Regex locations are likely to resolve to multiple places and offset locations
+			// are only meaningful at the time the breakpoint was created.
+			breakpoints[i].Message = fmt.Sprintf("breakpoint name %q could not be parsed as a function. name must be in the format 'funcName', 'funcName:line' or 'fileName:line'.", want.Name)
+			continue
+		}
+
+		if want.Name[0] == '.' {
+			breakpoints[i].Message = "breakpoint names that are relative paths are not supported."
+			continue
+		}
+		// Find the location of the function name. CreateBreakpoint requires the name to include the base
+		// (e.g. main.functionName is supported but not functionName).
+		// We first find the location of the function, and then set breakpoints for that location.
+		var locs []api.Location
+		locs, err = s.debugger.FindLocationSpec(-1, 0, 0, want.Name, spec, true, s.args.substitutePathClientToServer)
+		if err != nil {
+			breakpoints[i].Message = err.Error()
+			continue
+		}
+		if len(locs) == 0 {
+			breakpoints[i].Message = fmt.Sprintf("no location found for %q", want.Name)
+			continue
+		}
+		if len(locs) > 0 {
+			s.log.Debugf("multiple locations found for %s", want.Name)
+			breakpoints[i].Message = fmt.Sprintf("multiple locations found for %s, function breakpoint is only set for the first location", want.Name)
+		}
+
+		// Set breakpoint using the PCs that were found.
+		loc := locs[0]
+		got, err := s.debugger.CreateBreakpoint(&api.Breakpoint{Addr: loc.PC, Addrs: loc.PCs, Cond: want.Condition, Name: reqString})
+
+		var clientPath string
+		if got != nil {
+			clientPath = s.toClientPath(got.File)
+		}
+		updateBreakpointsResponse(breakpoints, i, err, got, clientPath)
 	}
+
+	response := &dap.SetFunctionBreakpointsResponse{Response: *newResponse(request.Request)}
+	response.Body.Breakpoints = breakpoints
+
 	s.send(response)
 }
 
+func (s *Server) clearBreakpoints(existingBps map[string]*api.Breakpoint, bpAdded map[string]struct{}) error {
+	for req, bp := range existingBps {
+		if _, ok := bpAdded[req]; ok {
+			continue
+		}
+		_, err := s.debugger.ClearBreakpoint(bp)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *Server) getMatchingBreakpoints(prefix string) map[string]*api.Breakpoint {
+	existing := s.debugger.Breakpoints()
+	matchingBps := make(map[string]*api.Breakpoint, len(existing))
+	for _, bp := range existing {
+		// Skip special breakpoints such as for panic.
+		if bp.ID < 0 {
+			continue
+		}
+		// Skip breakpoints that do not meet the condition.
+		if !strings.HasPrefix(bp.Name, prefix) {
+			continue
+		}
+		matchingBps[bp.Name] = bp
+	}
+	return matchingBps
+}
+
 func (s *Server) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) {
 	// Unlike what DAP documentation claims, this request is always sent
 	// even though we specified no filters at initialization. Handle as no-op.
@@ -1115,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.
@@ -1323,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
@@ -1333,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
@@ -1383,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)
@@ -1391,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)}
@@ -1427,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)
 	}
@@ -1456,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),
@@ -1468,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
@@ -1504,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
@@ -1530,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)
 			}
@@ -1544,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:
@@ -1590,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 ""
@@ -1613,39 +1960,56 @@ func (s *Server) getTypeIfSupported(v *proc.Variable) string {
 // custom, a zero reference, reminiscent of a zero pointer, is used to indicate that
 // a scalar variable cannot be "dereferenced" to get its elements (as there are none).
 func (s *Server) convertVariable(v *proc.Variable, qualifiedNameOrExpr string) (value string, variablesReference int) {
-	return s.convertVariableWithOpts(v, qualifiedNameOrExpr, false)
+	return s.convertVariableWithOpts(v, qualifiedNameOrExpr, 0)
 }
 
 func (s *Server) convertVariableToString(v *proc.Variable) string {
-	val, _ := s.convertVariableWithOpts(v, "", true)
+	val, _ := s.convertVariableWithOpts(v, "", skipRef)
 	return val
 }
 
+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
+
+const (
+	skipRef convertVariableFlags = 1 << iota
+	showFullValue
+)
+
 // convertVariableWithOpts allows to skip reference generation in case all we need is
-// a string representation of the variable.
-func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr string, skipRef bool) (value string, variablesReference int) {
+// a string representation of the variable. When the variable is a compound or reference
+// type variable and its full string representation can be larger than defaultMaxValueLen,
+// this returns a truncated value unless showFull option flag is set.
+func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr string, opts convertVariableFlags) (value string, variablesReference int) {
+	canHaveRef := false
 	maybeCreateVariableHandle := func(v *proc.Variable) int {
-		if skipRef {
+		canHaveRef = true
+		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 {
-		return value, variablesReference
+		return value, 0
 	}
 
 	// 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)
@@ -1702,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
 			}
 		}
@@ -1713,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
 			}
 		}
@@ -1761,6 +2125,13 @@ 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) > maxVarValueLen && canTruncateValue && canHaveRef {
+		value = value[:maxVarValueLen] + "..."
+	}
 	return value, variablesReference
 }
 
@@ -1808,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}
@@ -1818,22 +2189,30 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			return
 		}
 
-		if exprVar.Kind == reflect.String && (request.Arguments.Context == "repl" || request.Arguments.Context == "variables") {
-			if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
-				// reload string value with a bigger limit.
-				loadCfg := DefaultLoadConfig
-				loadCfg.MaxStringLen = 4 << 10
-				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 {
-					exprVar = v
+		ctxt := request.Arguments.Context
+		switch ctxt {
+		case "repl", "variables", "hover", "clipboard":
+			if exprVar.Kind == reflect.String {
+				if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
+					// Reload the string value with a bigger limit.
+					loadCfg := DefaultLoadConfig
+					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 {
+						exprVar = v
+					}
 				}
 			}
 		}
-		// TODO(polina): as far as I can tell, evaluateName is ignored by vscode for expression variables.
-		// Should it be skipped altogether for all levels?
-		exprVal, exprRef := s.convertVariable(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression))
-		response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef}
+		var opts convertVariableFlags
+		// 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, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)}
 	}
 	s.send(response)
 }
@@ -1854,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
@@ -1956,102 +2333,6 @@ func (s *Server) onRestartRequest(request *dap.RestartRequest) {
 	s.sendNotYetImplementedErrorResponse(request.Request)
 }
 
-// functionBpPrefix is the prefix of bp.Name for every breakpoint bp set
-// in this request.
-const functionBpPrefix = "functionBreakpoint"
-
-func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) {
-	if s.noDebugProcess != nil {
-		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "running in noDebug mode")
-		return
-	}
-
-	// According to the spec, setFunctionBreakpoints "replaces all existing function
-	// breakpoints with new function breakpoints." The simplest way is
-	// to clear all and then set all.
-
-	// Clear all existing function breakpoints in the file.
-	// Function breakpoints are set with the Name field set to be the
-	// function name. We cannot use FunctionName to determine this, because
-	// the debugger sets the function name for all breakpoints.
-	if err := s.clearMatchingBreakpoints(func(bp *api.Breakpoint) bool { return strings.HasPrefix(bp.Name, functionBpPrefix) }); err != nil {
-		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", err.Error())
-		return
-	}
-
-	// Set all requested breakpoints.
-	response := &dap.SetFunctionBreakpointsResponse{Response: *newResponse(request.Request)}
-	response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
-	for i, want := range request.Arguments.Breakpoints {
-		spec, err := locspec.Parse(want.Name)
-		if err != nil {
-			response.Body.Breakpoints[i].Message = err.Error()
-			continue
-		}
-		if loc, ok := spec.(*locspec.NormalLocationSpec); !ok || loc.FuncBase == nil {
-			// Other locations do not make sense in the context of function breakpoints.
-			// Regex locations are likely to resolve to multiple places and offset locations
-			// are only meaningful at the time the breakpoint was created.
-			response.Body.Breakpoints[i].Message = fmt.Sprintf("breakpoint name %q could not be parsed as a function. name must be in the format 'funcName', 'funcName:line' or 'fileName:line'.", want.Name)
-			continue
-		}
-
-		if want.Name[0] == '.' {
-			response.Body.Breakpoints[i].Message = "breakpoint names that are relative paths are not supported."
-			continue
-		}
-		// Find the location of the function name. CreateBreakpoint requires the name to include the base
-		// (e.g. main.functionName is supported but not functionName).
-		// We first find the location of the function, and then set breakpoints for that location.
-		locs, err := s.debugger.FindLocationSpec(-1, 0, 0, want.Name, spec, true, s.args.substitutePathClientToServer)
-		if err != nil {
-			response.Body.Breakpoints[i].Message = err.Error()
-			continue
-		}
-		if len(locs) == 0 {
-			response.Body.Breakpoints[i].Message = fmt.Sprintf("no location found for %q", want.Name)
-			continue
-		}
-		if len(locs) > 0 {
-			s.log.Debugf("multiple locations found for %s", want.Name)
-			response.Body.Breakpoints[i].Message = fmt.Sprintf("multiple locations found for %s, function breakpoint is only set for the first location", want.Name)
-		}
-
-		// Set breakpoint using the PCs that were found.
-		loc := locs[0]
-		got, err := s.debugger.CreateBreakpoint(
-			&api.Breakpoint{Name: fmt.Sprintf("%s%d", functionBpPrefix, i), Addr: loc.PC, Addrs: loc.PCs, Cond: want.Condition})
-		response.Body.Breakpoints[i].Verified = (err == nil)
-		if err != nil {
-			response.Body.Breakpoints[i].Message = err.Error()
-		} else {
-			response.Body.Breakpoints[i].Id = got.ID
-			response.Body.Breakpoints[i].Line = got.Line
-			response.Body.Breakpoints[i].Source = dap.Source{Name: filepath.Base(got.File), Path: got.File}
-		}
-	}
-	s.send(response)
-}
-
-func (s *Server) clearMatchingBreakpoints(matchCondition func(*api.Breakpoint) bool) error {
-	existing := s.debugger.Breakpoints()
-	for _, bp := range existing {
-		// Skip special breakpoints such as for panic.
-		if bp.ID < 0 {
-			continue
-		}
-		// Skip breakpoints that do not meet the condition.
-		if !matchCondition(bp) {
-			continue
-		}
-		_, err := s.debugger.ClearBreakpoint(bp)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 // onStepBackRequest sends a not-yet-implemented error response.
 // Capability 'supportsStepBack' is not set 'initialize' response.
 func (s *Server) onStepBackRequest(request *dap.StepBackRequest) {
@@ -2229,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 {
@@ -2265,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),
@@ -2408,10 +2722,10 @@ func (s *Server) doRunCommand(command string, asyncSetupDone chan struct{}) {
 			switch state.CurrentThread.Breakpoint.Name {
 			case proc.FatalThrow:
 				stopped.Body.Reason = "exception"
-				stopped.Body.Description = "Paused on fatal error"
+				stopped.Body.Description = "fatal error"
 			case proc.UnrecoveredPanic:
 				stopped.Body.Reason = "exception"
-				stopped.Body.Description = "Paused on panic"
+				stopped.Body.Description = "panic"
 			}
 			if strings.HasPrefix(state.CurrentThread.Breakpoint.Name, functionBpPrefix) {
 				stopped.Body.Reason = "function breakpoint"
@@ -2421,7 +2735,7 @@ func (s *Server) doRunCommand(command string, asyncSetupDone chan struct{}) {
 		s.exceptionErr = err
 		s.log.Error("runtime error: ", err)
 		stopped.Body.Reason = "exception"
-		stopped.Body.Description = "Paused on runtime error"
+		stopped.Body.Description = "runtime error"
 		stopped.Body.Text = err.Error()
 		// Special case in the spirit of https://github.com/microsoft/vscode-go/issues/1903
 		if stopped.Body.Text == "bad access" {
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 00ff9333..431ed1be 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -6,11 +6,13 @@ import (
 	"errors"
 	"fmt"
 	"go/parser"
+	"go/token"
 	"os"
 	"path/filepath"
 	"regexp"
 	"runtime"
 	"sort"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -235,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 {
@@ -629,9 +637,6 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
 	)
 
 	if requestedBp.Name != "" {
-		if err = api.ValidBreakpointName(requestedBp.Name); err != nil {
-			return nil, err
-		}
 		if (d.findBreakpointByName(requestedBp.Name) != nil) || (d.findDisabledBreakpointByName(requestedBp.Name) != nil) {
 			return nil, errors.New("breakpoint name already exists")
 		}
@@ -742,9 +747,6 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
 	if originals == nil && !disabled {
 		return fmt.Errorf("no breakpoint with ID %d", amend.ID)
 	}
-	if err := api.ValidBreakpointName(amend.Name); err != nil {
-		return err
-	}
 	if !amend.Disabled && disabled { // enable the breakpoint
 		bp, err := d.target.SetBreakpointWithID(amend.ID, amend.Addr)
 		if err != nil {
@@ -789,9 +791,63 @@ func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err err
 	if requested.Cond != "" {
 		bp.Cond, err = parser.ParseExpr(requested.Cond)
 	}
+	bp.HitCond = nil
+	if requested.HitCond != "" {
+		opTok, val, parseErr := parseHitCondition(requested.HitCond)
+		if err == nil {
+			err = parseErr
+		}
+		if parseErr == nil {
+			bp.HitCond = &struct {
+				Op  token.Token
+				Val int
+			}{opTok, val}
+		}
+	}
 	return err
 }
 
+func parseHitCondition(hitCond string) (token.Token, int, error) {
+	// A hit condition can be in the following formats:
+	// - "number"
+	// - "OP number"
+	hitConditionRegex := regexp.MustCompile(`((=|>|<|%|!)+|)( |)((\d|_)+)`)
+
+	match := hitConditionRegex.FindStringSubmatch(strings.TrimSpace(hitCond))
+	if match == nil || len(match) != 6 {
+		return 0, 0, fmt.Errorf("unable to parse breakpoint hit condition: %q\nhit conditions should be of the form \"number\" or \"OP number\"", hitCond)
+	}
+
+	opStr := match[1]
+	var opTok token.Token
+	switch opStr {
+	case "==", "":
+		opTok = token.EQL
+	case ">=":
+		opTok = token.GEQ
+	case "<=":
+		opTok = token.LEQ
+	case ">":
+		opTok = token.GTR
+	case "<":
+		opTok = token.LSS
+	case "%":
+		opTok = token.REM
+	case "!=":
+		opTok = token.NEQ
+	default:
+		return 0, 0, fmt.Errorf("unable to parse breakpoint hit condition: %q\ninvalid operator: %q", hitCond, opStr)
+	}
+
+	numStr := match[4]
+	val, parseErr := strconv.Atoi(numStr)
+	if parseErr != nil {
+		return 0, 0, fmt.Errorf("unable to parse breakpoint hit condition: %q\ninvalid number: %q", hitCond, numStr)
+	}
+
+	return opTok, val, nil
+}
+
 // ClearBreakpoint clears a breakpoint.
 func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
 	d.targetMutex.Lock()
@@ -956,7 +1012,7 @@ func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string,
 	if err != nil {
 		return nil, err
 	}
-	if api.ValidBreakpointName(expr) == nil && d.findBreakpointByName(expr) == nil {
+	if d.findBreakpointByName(expr) == nil {
 		bp.Name = expr
 	}
 	return api.ConvertBreakpoint(bp), nil
@@ -1139,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
@@ -1414,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}
 }
diff --git a/service/rpc1/server.go b/service/rpc1/server.go
index 558594dd..2844dc82 100644
--- a/service/rpc1/server.go
+++ b/service/rpc1/server.go
@@ -103,6 +103,9 @@ func (s *RPCServer) ListBreakpoints(arg interface{}, breakpoints *[]*api.Breakpo
 }
 
 func (s *RPCServer) CreateBreakpoint(bp, newBreakpoint *api.Breakpoint) error {
+	if err := api.ValidBreakpointName(bp.Name); err != nil {
+		return err
+	}
 	createdbp, err := s.debugger.CreateBreakpoint(bp)
 	if err != nil {
 		return err
@@ -139,6 +142,9 @@ func (s *RPCServer) ClearBreakpointByName(name string, breakpoint *api.Breakpoin
 
 func (s *RPCServer) AmendBreakpoint(amend *api.Breakpoint, unused *int) error {
 	*unused = 0
+	if err := api.ValidBreakpointName(amend.Name); err != nil {
+		return err
+	}
 	return s.debugger.AmendBreakpoint(amend)
 }
 
diff --git a/service/rpc2/server.go b/service/rpc2/server.go
index 3d5edb1c..05dbfbda 100644
--- a/service/rpc2/server.go
+++ b/service/rpc2/server.go
@@ -249,6 +249,9 @@ type CreateBreakpointOut struct {
 //
 // - Otherwise the value specified by arg.Breakpoint.Addr will be used.
 func (s *RPCServer) CreateBreakpoint(arg CreateBreakpointIn, out *CreateBreakpointOut) error {
+	if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil {
+		return err
+	}
 	createdbp, err := s.debugger.CreateBreakpoint(&arg.Breakpoint)
 	if err != nil {
 		return err
@@ -314,6 +317,9 @@ func (s *RPCServer) ToggleBreakpoint(arg ToggleBreakpointIn, out *ToggleBreakpoi
 		}
 	}
 	bp.Disabled = !bp.Disabled
+	if err := api.ValidBreakpointName(bp.Name); err != nil {
+		return err
+	}
 	if err := s.debugger.AmendBreakpoint(bp); err != nil {
 		return err
 	}
@@ -334,6 +340,9 @@ type AmendBreakpointOut struct {
 //
 // arg.Breakpoint.ID must be a valid breakpoint ID
 func (s *RPCServer) AmendBreakpoint(arg AmendBreakpointIn, out *AmendBreakpointOut) error {
+	if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil {
+		return err
+	}
 	return s.debugger.AmendBreakpoint(&arg.Breakpoint)
 }
 


Test patch:
diff --git a/_fixtures/fputest/fputest_amd64.s b/_fixtures/fputest/fputest_amd64.s
index 654e5e67..753c5acb 100644
--- a/_fixtures/fputest/fputest_amd64.s
+++ b/_fixtures/fputest/fputest_amd64.s
@@ -56,6 +56,8 @@ TEXT ·fputestsetup(SB),$0-50
 	MOVAPS X1, X9
 	MOVAPS X2, X10
 	
+	MOVQ $42, AX
+	
 	CMPB avx2+48(FP), $0x0
 	JE done
 	//copy XMM1 to both halves of YMM11
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 ed9aa16c..a455a445 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -1685,6 +1685,78 @@ func TestCondBreakpointError(t *testing.T) {
 	})
 }
 
+func TestHitCondBreakpointEQ(t *testing.T) {
+	withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
+		bp := setFileBreakpoint(p, t, fixture.Source, 7)
+		bp.HitCond = &struct {
+			Op  token.Token
+			Val int
+		}{token.EQL, 3}
+
+		assertNoError(p.Continue(), t, "Continue()")
+		ivar := evalVariable(p, t, "i")
+
+		i, _ := constant.Int64Val(ivar.Value)
+		if i != 3 {
+			t.Fatalf("Stoppend on wrong hitcount %d\n", i)
+		}
+
+		err := p.Continue()
+		if _, exited := err.(proc.ErrProcessExited); !exited {
+			t.Fatalf("Unexpected error on Continue(): %v", err)
+		}
+	})
+}
+
+func TestHitCondBreakpointGEQ(t *testing.T) {
+	protest.AllowRecording(t)
+	withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
+		bp := setFileBreakpoint(p, t, fixture.Source, 7)
+		bp.HitCond = &struct {
+			Op  token.Token
+			Val int
+		}{token.GEQ, 3}
+
+		for it := 3; it <= 10; it++ {
+			assertNoError(p.Continue(), t, "Continue()")
+			ivar := evalVariable(p, t, "i")
+
+			i, _ := constant.Int64Val(ivar.Value)
+			if int(i) != it {
+				t.Fatalf("Stoppend on wrong hitcount %d\n", i)
+			}
+		}
+
+		assertNoError(p.Continue(), t, "Continue()")
+	})
+}
+
+func TestHitCondBreakpointREM(t *testing.T) {
+	protest.AllowRecording(t)
+	withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
+		bp := setFileBreakpoint(p, t, fixture.Source, 7)
+		bp.HitCond = &struct {
+			Op  token.Token
+			Val int
+		}{token.REM, 2}
+
+		for it := 2; it <= 10; it += 2 {
+			assertNoError(p.Continue(), t, "Continue()")
+			ivar := evalVariable(p, t, "i")
+
+			i, _ := constant.Int64Val(ivar.Value)
+			if int(i) != it {
+				t.Fatalf("Stoppend on wrong hitcount %d\n", i)
+			}
+		}
+
+		err := p.Continue()
+		if _, exited := err.(proc.ErrProcessExited); !exited {
+			t.Fatalf("Unexpected error on Continue(): %v", err)
+		}
+	})
+}
+
 func TestIssue356(t *testing.T) {
 	// slice with a typedef does not get printed correctly
 	protest.AllowRecording(t)
@@ -2670,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")
@@ -3226,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 {
@@ -3364,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 86fcac1d..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")
@@ -1178,3 +1179,16 @@ func TestPrintFormat(t *testing.T) {
 		}
 	})
 }
+
+func TestHitCondBreakpoint(t *testing.T) {
+	withTestTerminal("break", t, func(term *FakeTerminal) {
+		term.MustExec("break bp1 main.main:4")
+		term.MustExec("condition -hitcount bp1 > 2")
+		listIsAt(t, term, "continue", 7, -1, -1)
+		out := term.MustExec("print i")
+		t.Logf("%q", out)
+		if !strings.Contains(out, "3\n") {
+			t.Fatalf("wrong value of i")
+		}
+	})
+}
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index cb2036b7..3ad61ae1 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -104,6 +104,7 @@ func (c *Client) ExpectInitializeResponseAndCapabilities(t *testing.T) *dap.Init
 		SupportsSetVariable:              true,
 		SupportsFunctionBreakpoints:      true,
 		SupportsEvaluateForHovers:        true,
+		SupportsClipboardContext:         true,
 	}
 	if !reflect.DeepEqual(initResp.Body, wantCapabilities) {
 		t.Errorf("capabilities in initializeResponse: got %+v, want %v", pretty(initResp.Body), pretty(wantCapabilities))
@@ -235,7 +236,6 @@ func (c *Client) SetConditionalBreakpointsRequest(file string, lines []int, cond
 			Path: file,
 		},
 		Breakpoints: make([]dap.SourceBreakpoint, len(lines)),
-		//sourceModified: false,
 	}
 	for i, l := range lines {
 		request.Arguments.Breakpoints[i].Line = l
@@ -247,6 +247,26 @@ func (c *Client) SetConditionalBreakpointsRequest(file string, lines []int, cond
 	c.send(request)
 }
 
+// SetBreakpointsRequest sends a 'setBreakpoints' request with conditions.
+func (c *Client) SetHitConditionalBreakpointsRequest(file string, lines []int, conditions map[int]string) {
+	request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setBreakpoints")}
+	request.Arguments = dap.SetBreakpointsArguments{
+		Source: dap.Source{
+			Name: filepath.Base(file),
+			Path: file,
+		},
+		Breakpoints: make([]dap.SourceBreakpoint, len(lines)),
+	}
+	for i, l := range lines {
+		request.Arguments.Breakpoints[i].Line = l
+		cond, ok := conditions[l]
+		if ok {
+			request.Arguments.Breakpoints[i].HitCondition = cond
+		}
+	}
+	c.send(request)
+}
+
 // SetExceptionBreakpointsRequest sends a 'setExceptionBreakpoints' request.
 func (c *Client) SetExceptionBreakpointsRequest() {
 	request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setExceptionBreakpoints")}
@@ -323,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/daptest/gen/main.go b/service/dap/daptest/gen/main.go
index d1d95b6c..b7801cef 100644
--- a/service/dap/daptest/gen/main.go
+++ b/service/dap/daptest/gen/main.go
@@ -43,7 +43,12 @@ func (c *Client) Check{{.}}(t *testing.T, m dap.Message) *dap.{{.}} {
 	r, ok := m.(*dap.{{.}})
 	if !ok {
 		t.Fatalf("got %#v, want *dap.{{.}}", m)
-	}
+	}{{if or (or (eq . "StepInResponse") (eq . "StepOutResponse")) (eq . "NextResponse") }}
+	m = c.ExpectMessage(t)
+	_, ok = m.(*dap.ContinuedEvent)
+	if !ok {
+		t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
+	}{{end}}
 	return r
 }{{end}}
 `))
diff --git a/service/dap/daptest/resp.go b/service/dap/daptest/resp.go
index 3ecb24cd..dfac8636 100644
--- a/service/dap/daptest/resp.go
+++ b/service/dap/daptest/resp.go
@@ -492,6 +492,11 @@ func (c *Client) CheckNextResponse(t *testing.T, m dap.Message) *dap.NextRespons
 	if !ok {
 		t.Fatalf("got %#v, want *dap.NextResponse", m)
 	}
+	m = c.ExpectMessage(t)
+	_, ok = m.(*dap.ContinuedEvent)
+	if !ok {
+		t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
+	}
 	return r
 }
 
@@ -906,6 +911,11 @@ func (c *Client) CheckStepInResponse(t *testing.T, m dap.Message) *dap.StepInRes
 	if !ok {
 		t.Fatalf("got %#v, want *dap.StepInResponse", m)
 	}
+	m = c.ExpectMessage(t)
+	_, ok = m.(*dap.ContinuedEvent)
+	if !ok {
+		t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
+	}
 	return r
 }
 
@@ -942,6 +952,11 @@ func (c *Client) CheckStepOutResponse(t *testing.T, m dap.Message) *dap.StepOutR
 	if !ok {
 		t.Fatalf("got %#v, want *dap.StepOutResponse", m)
 	}
+	m = c.ExpectMessage(t)
+	_, ok = m.(*dap.ContinuedEvent)
+	if !ok {
+		t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
+	}
 	return r
 }
 
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 3b989b90..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)
 
-					// 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)
+					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 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 \[.+\.\.\.\+2 more\]`, `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
@@ -1572,7 +1654,7 @@ func TestVariablesLoading(t *testing.T) {
 							client.VariablesRequest(ref)
 							tmV := client.ExpectVariablesResponse(t)
 							checkChildren(t, tmV, "tm.v", 1)
-							ref = checkVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
+							ref = checkVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
 							if ref > 0 {
 								client.VariablesRequest(ref)
 								tmV0 := client.ExpectVariablesResponse(t)
@@ -1602,7 +1684,7 @@ func TestVariablesLoading(t *testing.T) {
 								tmV := client.ExpectVariablesResponse(t)
 								checkChildren(t, tmV, "tm.v", 1)
 								// TODO(polina): this evaluate name is not usable - it should be empty
-								ref = checkVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
+								ref = checkVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
 								if ref > 0 {
 									client.VariablesRequest(ref)
 									tmV0 := client.ExpectVariablesResponse(t)
@@ -1697,7 +1779,6 @@ func TestVariablesLoading(t *testing.T) {
 					// step into another function
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
-					client.ExpectContinuedEvent(t)
 					client.ExpectStoppedEvent(t)
 					checkStop(t, client, 1, "main.barfoo", 24)
 					loadvars(1001 /*second frame here is same as topmost above*/)
@@ -1707,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
@@ -1745,7 +1909,6 @@ func TestGlobalScopeAndVariables(t *testing.T) {
 					// Step into pkg.AnotherMethod()
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
-					client.ExpectContinuedEvent(t)
 					client.ExpectStoppedEvent(t)
 
 					client.StackTraceRequest(1, 0, 20)
@@ -1865,6 +2028,12 @@ func checkBreakpoints(t *testing.T, client *daptest.Client, bps []Breakpoint, br
 		return
 	}
 	for i, bp := range breakpoints {
+		if bps[i].line < 0 && !bps[i].verified {
+			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
+		}
 		if bp.Line != bps[i].line || bp.Verified != bps[i].verified || bp.Source.Path != bps[i].path ||
 			!strings.HasPrefix(bp.Message, bps[i].msgPrefix) {
 			t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
@@ -1903,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, ""}, {8, "", false, "Breakpoint exists"}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {-1, "", false, "breakpoint exists"}})
 
 					// Continue into the loop
 					client.ContinueRequest(1)
@@ -1942,7 +2111,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 					// Set at a line without a statement
 					client.SetBreakpointsRequest(fixture.Source, []int{1000})
-					expectSetBreakpointsResponse(t, client, []Breakpoint{{1000, "", false, "could not find statement"}}) // all cleared, none set
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{-1, "", false, "could not find statement"}}) // all cleared, none set
 				},
 				// The program has an infinite loop, so we must kill it by disconnecting.
 				disconnect: true,
@@ -1950,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) {
@@ -1978,8 +2151,8 @@ func TestSetFunctionBreakpoints(t *testing.T) {
 							return
 						}
 						for i, bp := range got.Body.Breakpoints {
-							if bps[i].line < 0 {
-								if bp.Verified != bps[i].verified || !strings.Contains(bp.Message, bps[i].errMsg) {
+							if bps[i].line < 0 && !bps[i].verified {
+								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
@@ -2126,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{
@@ -2254,7 +2427,6 @@ func TestSetBreakpointWhileRunning(t *testing.T) {
 					// We can set breakpoints while nexting
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 					client.SetBreakpointsRequest(fixture.Source, []int{15}) // [16,] => [15,]
 					se, br := expectSetBreakpointsResponseAndStoppedEvent(t, client)
 					if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
@@ -2310,7 +2482,6 @@ func TestSetFunctionBreakpointWhileRunning(t *testing.T) {
 					// We can set breakpoints while nexting
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{{Name: "main.sayhi"}}) // [16,] => [16, 8]
 					se, br := expectSetFunctionBreakpointsResponseAndStoppedEvent(t, client)
 					if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
@@ -2361,6 +2532,71 @@ func TestSetFunctionBreakpointWhileRunning(t *testing.T) {
 	})
 }
 
+func TestHitConditionBreakpoints(t *testing.T) {
+	runTest(t, "break", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{4},
+			[]onBreakpoint{{
+				execute: func() {
+					client.SetHitConditionalBreakpointsRequest(fixture.Source, []int{7}, map[int]string{7: "4"})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{7, fixture.Source, true, ""}})
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.ExpectStoppedEvent(t)
+					checkStop(t, client, 1, "main.main", 7)
+
+					// Check that we are stopped at the correct value of i.
+					client.VariablesRequest(1001)
+					locals := client.ExpectVariablesResponse(t)
+					checkVarExact(t, locals, 0, "i", "i", "4", "int", noChildren)
+
+					// Change the hit condition.
+					client.SetHitConditionalBreakpointsRequest(fixture.Source, []int{7}, map[int]string{7: "% 2"})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{7, fixture.Source, true, ""}})
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.ExpectStoppedEvent(t)
+					checkStop(t, client, 1, "main.main", 7)
+
+					// Check that we are stopped at the correct value of i.
+					client.VariablesRequest(1001)
+					locals = client.ExpectVariablesResponse(t)
+					checkVarExact(t, locals, 0, "i", "i", "6", "int", noChildren)
+
+					// Expect an error if an assignment is passed.
+					client.SetHitConditionalBreakpointsRequest(fixture.Source, []int{7}, map[int]string{7: "= 2"})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{-1, "", false, ""}})
+
+					// Change the hit condition.
+					client.SetHitConditionalBreakpointsRequest(fixture.Source, []int{7}, map[int]string{7: "< 8"})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{7, fixture.Source, true, ""}})
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.ExpectStoppedEvent(t)
+					checkStop(t, client, 1, "main.main", 7)
+
+					// Check that we are stopped at the correct value of i.
+					client.VariablesRequest(1001)
+					locals = client.ExpectVariablesResponse(t)
+					checkVarExact(t, locals, 0, "i", "i", "7", "int", noChildren)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+
+					client.ExpectTerminatedEvent(t)
+				},
+				disconnect: false,
+			}})
+	})
+}
+
 // TestLaunchSubstitutePath sets a breakpoint using a path
 // that does not exist and expects the substitutePath attribute
 // in the launch configuration to take care of the mapping.
@@ -2498,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)
@@ -2540,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")
@@ -2572,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")
@@ -2656,55 +2904,170 @@ func TestEvaluateRequest(t *testing.T) {
 					if erres.Body.Error.Format != "Unable to evaluate expression: could not find symbol value for a1" {
 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
 					}
+					client.EvaluateRequest("a1", 1002, "clipboard")
+					erres = client.ExpectVisibleErrorResponse(t)
+					if erres.Body.Error.Format != "Unable to evaluate expression: could not find symbol value for a1" {
+						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
+					}
 				},
 				disconnect: false,
 			}})
 	})
 }
 
-func TestEvaluateRequestLongStr(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)
 
-					longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
-					longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
+					client.VariablesRequest(1001) // Locals
+					locals := client.ExpectVariablesResponse(t)
 
+					// 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]+}, )+.+\.\.\.`
+
+					// In variable responses
+					checkVarRegex(t, locals, -1, "m1", "m1", m1Part, `map\[string\]main\.astruct`, hasChildren)
+
+					// 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)
 
-					// reflect.Kind == String, load with longer load limit if evaluated in repl/variables context.
-					for _, evalContext := range []string{"", "watch", "repl", "variables", "somethingelse"} {
-						t.Run(evalContext, func(t *testing.T) {
-							// long string
-							client.EvaluateRequest("longstr", 0, evalContext)
-							got := client.ExpectEvaluateResponse(t)
-							want := longstrTruncated
-							if evalContext == "repl" || evalContext == "variables" {
-								want = longstr
+					// 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)
 							}
-							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)
+							checkEvalRegex(t, client.ExpectEvaluateResponse(t), want, hasChildren)
+						})
+					}
 
-							// 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)
+					// 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,
 			}})
@@ -2841,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")
@@ -2902,22 +3258,18 @@ func TestNextAndStep(t *testing.T) {
 
 					client.StepOutRequest(1)
 					client.ExpectStepOutResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStop("main.main", 18)
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStop("main.main", 19)
 
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStop("main.inlineThis", 5)
 
 					client.NextRequest(-1000)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 					if se := client.ExpectStoppedEvent(t); se.Body.Reason != "error" || se.Body.Text != "unknown goroutine -1000" {
 						t.Errorf("got %#v, want Reason=\"error\", Text=\"unknown goroutine -1000\"", se)
 					}
@@ -2946,7 +3298,6 @@ func TestNextParked(t *testing.T) {
 
 					client.NextRequest(goroutineId)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 
 					se := client.ExpectStoppedEvent(t)
 					if se.Body.ThreadId != goroutineId {
@@ -2976,7 +3327,6 @@ func TestStepInParked(t *testing.T) {
 
 					client.StepInRequest(goroutineId)
 					client.ExpectStepInResponse(t)
-					client.ExpectContinuedEvent(t)
 
 					se := client.ExpectStoppedEvent(t)
 					if se.Body.ThreadId != goroutineId {
@@ -3096,7 +3446,6 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
 					}
 					client.StepOutRequest(goroutineId)
 					client.ExpectStepOutResponse(t)
-					client.ExpectContinuedEvent(t)
 
 					switch e := client.ExpectMessage(t).(type) {
 					case *dap.StoppedEvent:
@@ -3133,15 +3482,14 @@ func TestBadAccess(t *testing.T) {
 					expectStoppedOnError := func(errorPrefix string) {
 						t.Helper()
 						se := client.ExpectStoppedEvent(t)
-						if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "Paused on runtime error" || !strings.HasPrefix(se.Body.Text, errorPrefix) {
-							t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"Paused on runtime error\" Text=\"%s\"", se, errorPrefix)
+						if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "runtime error" || !strings.HasPrefix(se.Body.Text, errorPrefix) {
+							t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"runtime error\" Text=\"%s\"", se, errorPrefix)
 						}
 						client.ExceptionInfoRequest(1)
 						eInfo := client.ExpectExceptionInfoResponse(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)
@@ -3150,22 +3498,18 @@ func TestBadAccess(t *testing.T) {
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("invalid memory address or nil pointer dereference")
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("next while nexting")
 
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("next while nexting")
 
 					client.StepOutRequest(1)
 					client.ExpectStepOutResponse(t)
-					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("next while nexting")
 				},
 				disconnect: true,
@@ -3190,8 +3534,8 @@ func TestPanicBreakpointOnContinue(t *testing.T) {
 					client.ExpectContinueResponse(t)
 
 					se := client.ExpectStoppedEvent(t)
-					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "Paused on panic" {
-						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"Paused on panic\"", se)
+					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "panic" {
+						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"panic\"", se)
 					}
 
 					client.ExceptionInfoRequest(1)
@@ -3199,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,
 			}})
@@ -3226,11 +3583,10 @@ func TestPanicBreakpointOnNext(t *testing.T) {
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
-					client.ExpectContinuedEvent(t)
 
 					se := client.ExpectStoppedEvent(t)
-					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "Paused on panic" {
-						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"Paused on panic\"", se)
+					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "panic" {
+						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"panic\"", se)
 					}
 
 					client.ExceptionInfoRequest(1)
@@ -3245,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
@@ -3261,9 +3652,13 @@ func TestFatalThrowBreakpoint(t *testing.T) {
 					client.ExpectContinueResponse(t)
 
 					se := client.ExpectStoppedEvent(t)
-					if se.Body.Reason != "exception" || se.Body.Description != "Paused on fatal error" {
-						t.Errorf("\ngot  %#v\nwant Reason=\"exception\" Description=\"Paused on fatal error\"", se)
+					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,
 			}})
@@ -3430,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 {
@@ -3482,7 +3877,7 @@ func TestLaunchRequestNoDebug_GoodStatus(t *testing.T) {
 				"noDebug": true,
 				"mode":    "debug",
 				"program": fixture.Source,
-				"output":  cleanExeName("__mybin")})
+				"output":  "__mybin"})
 		}, fixture.Source, []int{8}, 0)
 	})
 }
@@ -3494,7 +3889,7 @@ func TestLaunchRequestNoDebug_BadStatus(t *testing.T) {
 				"noDebug": true,
 				"mode":    "debug",
 				"program": fixture.Source,
-				"output":  cleanExeName("__mybin")})
+				"output":  "__mybin"})
 		}, fixture.Source, []int{8}, 2)
 	})
 }
@@ -3743,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)
 	}
 }
@@ -3872,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 f5c54775..a59e13df 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1598,6 +1598,7 @@ func TestClientServer_FpRegisters(t *testing.T) {
 			{"XMM1.uint16[0]", `52429`},
 			{"XMM1.float32[0]", `-107374184`},
 			{"XMM1.float64[0]", `1.3`},
+			{"RAX.uint8[0]", "42"},
 		} {
 			v, err := c.EvalVariable(scope, tc.expr, normalLoadConfig)
 			if err != nil {
@@ -1772,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)
+		}
 	})
 }