Repository: go-delve/delve
Pull request #2446 (2021-04-22 13:50:36)

proc: add way to use CPU registers in expressions
Changes the expression evaluation code so that register names, when not
shadowed by local or global variables, will evaluate to the current
value of the corresponding CPU register.

This allows a greater flexibility with displaying CPU registers than is
possible with using the ListRegisters API call. Also it allows
debuggers users to view register values even if the frontend they are
using does not implement a register view.


derekparker: This is a cool idea, I like it a lot. My initial thoughts for the shadowing problem is why not `$RAX` or `%RAX` instead of `_RAX`?
aarzilli: > This is a cool idea, I like it a lot. My initial thoughts for the shadowing problem is why not `$RAX` or `%RAX` instead of `_RAX`?

We'd have to write our own parser, instead of using `go/parser`. I don't think it's worth it.

Patch:
diff --git a/Documentation/api/ClientHowto.md b/Documentation/api/ClientHowto.md
index dc58a8f3..34807c13 100644
--- a/Documentation/api/ClientHowto.md
+++ b/Documentation/api/ClientHowto.md
@@ -33,7 +33,7 @@ This will, however, cause problems if you actually spawn multiple instances of t
 
 It's probably better to let Delve pick a random unused port number on its own. To do this do not specify any `--listen` option and read one line of output from dlv's stdout. If the first line emitted by dlv starts with "API server listening at: " then dlv started correctly and the rest of the line specifies the address that Delve is listening at.
 
-The `--log-to-file` and `--log-to-fd` options can be used to redirect the "API server listening at:" message to a file or to a file descriptor. If neither is specified the message will be output to stdout.
+The `--log-dest` option can be used to redirect the "API server listening at:" message to a file or to a file descriptor. If the flag is not specified, the message will be output to stdout while other log messages are output to stderr.
 
 ## Controlling the backend
 
diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md
index 3d1804e4..94a5a14c 100644
--- a/Documentation/cli/README.md
+++ b/Documentation/cli/README.md
@@ -431,7 +431,7 @@ Print contents of CPU registers.
 
 	regs [-a]
 
-Argument -a shows more registers.
+Argument -a shows more registers. Individual registers can also be displayed by 'print' and 'display'. See [Documentation/cli/expr.md.](//github.com/go-delve/delve/tree/master/Documentation/cli/expr.md.)
 
 
 ## restart
diff --git a/Documentation/cli/expr.md b/Documentation/cli/expr.md
index fcf027f0..89d94590 100644
--- a/Documentation/cli/expr.md
+++ b/Documentation/cli/expr.md
@@ -110,3 +110,22 @@ Packages with the same name can be disambiguated by using the full package path.
 # Pointers in Cgo
 
 Char pointers are always treated as NUL terminated strings, both indexing and the slice operator can be applied to them. Other C pointers can also be used similarly to Go slices, with indexing and the slice operator. In both of these cases it is up to the user to respect array bounds.
+
+
+# CPU Registers
+
+The name of a CPU register, in all uppercase letters, will resolve to the value of that CPU register in the current frame. For example on AMD64 the expression `RAX` will evaluate to the value of the RAX register. 
+
+Register names are shadowed by both local and global variables, so if a local variable called "RAX" exists, the `RAX` expression will evaluate to it instead of the CPU register.
+
+Register names can optionally be prefixed by any number of underscore characters, so `RAX`, `_RAX`, `__RAX`, etc... can all be used to refer to the same RAX register and, in absence of shadowing from other variables, will all evaluate to the same value.
+
+Registers of 64bits or less are returned as uint64 variables. Larger registers are returned as strings of hexadecimal digits.
+
+Because many architectures have SIMD registers that can be used by the application in different ways the following syntax is also available:
+
+* `REGNAME.intN` returns the register REGNAME as an array of intN elements.
+* `REGNAME.uintN` returns the register REGNAME as an array of uintN elements.
+* `REGNAME.floatN` returns the register REGNAME as an array fo floatN elements.
+
+In all cases N must be a power of 2.
diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go
index 2e94261f..3d097b26 100644
--- a/cmd/dlv/cmds/commands.go
+++ b/cmd/dlv/cmds/commands.go
@@ -253,7 +253,11 @@ or later, -gcflags="-N -l" on earlier versions of Go.`,
 The test command allows you to begin a new debug session in the context of your
 unit tests. By default Delve will debug the tests in the current directory.
 Alternatively you can specify a package name, and Delve will debug the tests in
-that package instead.`,
+that package instead. Double-dashes ` + "`--`" + ` can be used to pass arguments to the test program:
+
+dlv test [package] -- -test.v -other-argument
+
+See also: 'go help testflag'.`,
 		Run: testCmd,
 	}
 	testCommand.Flags().String("output", "debug.test", "Output path for the binary.")
diff --git a/go.mod b/go.mod
index fed3c715..ac6035a8 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/cosiner/argv v0.1.0
 	github.com/cpuguy83/go-md2man v1.0.10 // indirect
 	github.com/creack/pty v1.1.9
-	github.com/google/go-dap v0.4.0
+	github.com/google/go-dap v0.5.0
 	github.com/hashicorp/golang-lru v0.5.4
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561
diff --git a/go.sum b/go.sum
index d24b4547..3d011c8f 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5 h1:rIXlvz2IWiupMFlC45cZCXZFvKX/ExBcSLrDy2G0Lp8=
-github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ=
 github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
 github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
 github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
@@ -12,12 +10,8 @@ github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/google/go-dap v0.2.0 h1:whjIGQRumwbR40qRU7CEKuFLmePUUc2s4Nt9DoXXxWk=
-github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
-github.com/google/go-dap v0.3.0 h1:Dc4izN0u4VhZERYrz80f1PSEoDsVfdVmdM/W82CxzFk=
-github.com/google/go-dap v0.3.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
-github.com/google/go-dap v0.4.0 h1:bWSjcM9zp/jEFD4YbWERcHSed8vHbEdk0rmTvqgXDAs=
-github.com/google/go-dap v0.4.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
+github.com/google/go-dap v0.5.0 h1:RMHAVn5xeunBakYk65ggHXttk6qjZVdbmi+xhAoL2wY=
+github.com/google/go-dap v0.5.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -42,8 +36,6 @@ github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1 h1:7bozMfSdo41n2NOc0Gs
 github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-go.starlark.net v0.0.0-20190702223751-32f345186213 h1:lkYv5AKwvvduv5XWP6szk/bvvgO6aDeUujhZQXIFTes=
-go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
 go.starlark.net v0.0.0-20200821142938-949cc6f4b097 h1:YiRMXXgG+Pg26t1fjq+iAjaauKWMC9cmGFrtOEuwDDg=
 go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
 golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=
@@ -56,17 +48,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
-golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY=
 golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200415034506-5d8e1897c761 h1:FVw4lelfGRNPqB3C8qX1m+QyeM2vzToIwlFhEZX42y8=
-golang.org/x/tools v0.0.0-20200415034506-5d8e1897c761/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go
index a6babe16..09dd6de7 100644
--- a/pkg/dwarf/line/line_parser.go
+++ b/pkg/dwarf/line/line_parser.go
@@ -89,11 +89,19 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{})
 
 	parseDebugLinePrologue(dbl, buf)
 	if dbl.Prologue.Version >= 5 {
-		parseIncludeDirs5(dbl, buf)
-		parseFileEntries5(dbl, buf)
+		if !parseIncludeDirs5(dbl, buf) {
+			return nil
+		}
+		if !parseFileEntries5(dbl, buf) {
+			return nil
+		}
 	} else {
-		parseIncludeDirs2(dbl, buf)
-		parseFileEntries2(dbl, buf)
+		if !parseIncludeDirs2(dbl, buf) {
+			return nil
+		}
+		if !parseFileEntries2(dbl, buf) {
+			return nil
+		}
 	}
 
 	// Instructions size calculation breakdown:
@@ -135,20 +143,30 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
 }
 
 // parseIncludeDirs2 parses the directory table for DWARF version 2 through 4.
-func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) {
+func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) bool {
 	for {
-		str, _ := util.ParseString(buf)
+		str, err := util.ParseString(buf)
+		if err != nil {
+			if info.Logf != nil {
+				info.Logf("error reading string: %v", err)
+			}
+			return false
+		}
 		if str == "" {
 			break
 		}
 
 		info.IncludeDirs = append(info.IncludeDirs, str)
 	}
+	return true
 }
 
 // parseIncludeDirs5 parses the directory table for DWARF version 5.
-func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) {
+func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool {
 	dirEntryFormReader := readEntryFormat(buf, info.Logf)
+	if dirEntryFormReader == nil {
+		return false
+	}
 	dirCount, _ := util.DecodeULEB128(buf)
 	info.IncludeDirs = make([]string, 0, dirCount)
 	for i := uint64(0); i < dirCount; i++ {
@@ -168,13 +186,23 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) {
 			case _DW_LNCT_MD5:
 			}
 		}
+		if dirEntryFormReader.err != nil {
+			if info.Logf != nil {
+				info.Logf("error reading directory entries table: %v", dirEntryFormReader.err)
+			}
+			return false
+		}
 	}
+	return true
 }
 
 // parseFileEntries2 parses the file table for DWARF 2 through 4
-func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) {
+func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) bool {
 	for {
 		entry := readFileEntry(info, buf, true)
+		if entry == nil {
+			return false
+		}
 		if entry.Path == "" {
 			break
 		}
@@ -182,12 +210,20 @@ func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) {
 		info.FileNames = append(info.FileNames, entry)
 		info.Lookup[entry.Path] = entry
 	}
+	return true
 }
 
 func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry {
 	entry := new(FileEntry)
 
-	entry.Path, _ = util.ParseString(buf)
+	var err error
+	entry.Path, err = util.ParseString(buf)
+	if err != nil {
+		if info.Logf != nil {
+			info.Logf("error reading file entry: %v", err)
+		}
+		return nil
+	}
 	if entry.Path == "" && exitOnEmptyPath {
 		return entry
 	}
@@ -225,8 +261,11 @@ func pathIsAbs(s string) bool {
 }
 
 // parseFileEntries5 parses the file table for DWARF 5
-func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) {
+func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool {
 	fileEntryFormReader := readEntryFormat(buf, info.Logf)
+	if fileEntryFormReader == nil {
+		return false
+	}
 	fileCount, _ := util.DecodeULEB128(buf)
 	info.FileNames = make([]*FileEntry, 0, fileCount)
 	for i := 0; i < int(fileCount); i++ {
@@ -265,5 +304,12 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) {
 			info.FileNames = append(info.FileNames, entry)
 			info.Lookup[entry.Path] = entry
 		}
+		if fileEntryFormReader.err != nil {
+			if info.Logf != nil {
+				info.Logf("error reading file entries table: %v", fileEntryFormReader.err)
+			}
+			return false
+		}
 	}
+	return true
 }
diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go
index 10dd9633..6f96569e 100644
--- a/pkg/dwarf/line/parse_util.go
+++ b/pkg/dwarf/line/parse_util.go
@@ -3,6 +3,7 @@ package line
 import (
 	"bytes"
 	"encoding/binary"
+	"errors"
 
 	"github.com/go-delve/delve/pkg/dwarf/util"
 )
@@ -39,6 +40,8 @@ const (
 	_DW_LNCT_MD5
 )
 
+var ErrBufferUnderflow = errors.New("buffer underflow")
+
 type formReader struct {
 	logf         func(string, ...interface{})
 	contentTypes []uint64
@@ -51,11 +54,15 @@ type formReader struct {
 	u64   uint64
 	i64   int64
 	str   string
+	err   error
 
 	nexti int
 }
 
 func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *formReader {
+	if buf.Len() < 1 {
+		return nil
+	}
 	count := buf.Next(1)[0]
 	r := &formReader{
 		logf:         logf,
@@ -70,10 +77,14 @@ func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *form
 }
 
 func (rdr *formReader) reset() {
+	rdr.err = nil
 	rdr.nexti = 0
 }
 
 func (rdr *formReader) next(buf *bytes.Buffer) bool {
+	if rdr.err != nil {
+		return false
+	}
 	if rdr.nexti >= len(rdr.contentTypes) {
 		return false
 	}
@@ -87,24 +98,52 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
 		rdr.readBlock(buf, n)
 
 	case _DW_FORM_block1:
+		if buf.Len() < 1 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.readBlock(buf, uint64(buf.Next(1)[0]))
 
 	case _DW_FORM_block2:
+		if buf.Len() < 2 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.readBlock(buf, uint64(binary.LittleEndian.Uint16(buf.Next(2))))
 
 	case _DW_FORM_block4:
+		if buf.Len() < 4 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.readBlock(buf, uint64(binary.LittleEndian.Uint32(buf.Next(4))))
 
 	case _DW_FORM_data1, _DW_FORM_flag, _DW_FORM_strx1:
+		if buf.Len() < 1 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.u64 = uint64(buf.Next(1)[0])
 
 	case _DW_FORM_data2, _DW_FORM_strx2:
+		if buf.Len() < 2 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.u64 = uint64(binary.LittleEndian.Uint16(buf.Next(2)))
 
 	case _DW_FORM_data4, _DW_FORM_line_strp, _DW_FORM_sec_offset, _DW_FORM_strp, _DW_FORM_strx4:
+		if buf.Len() < 4 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.u64 = uint64(binary.LittleEndian.Uint32(buf.Next(4)))
 
 	case _DW_FORM_data8:
+		if buf.Len() < 8 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.u64 = binary.LittleEndian.Uint64(buf.Next(8))
 
 	case _DW_FORM_data16:
@@ -120,6 +159,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
 		rdr.str, _ = util.ParseString(buf)
 
 	case _DW_FORM_strx3:
+		if buf.Len() < 3 {
+			rdr.err = ErrBufferUnderflow
+			return false
+		}
 		rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0)))
 
 	default:
@@ -136,6 +179,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
 }
 
 func (rdr *formReader) readBlock(buf *bytes.Buffer, n uint64) {
+	if uint64(buf.Len()) < n {
+		rdr.err = ErrBufferUnderflow
+		return
+	}
 	if cap(rdr.block) < int(n) {
 		rdr.block = make([]byte, 0, n)
 	}
diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go
index ddc83410..d7e11c26 100644
--- a/pkg/dwarf/line/state_machine.go
+++ b/pkg/dwarf/line/state_machine.go
@@ -552,8 +552,9 @@ func setdiscriminator(sm *StateMachine, buf *bytes.Buffer) {
 }
 
 func definefile(sm *StateMachine, buf *bytes.Buffer) {
-	entry := readFileEntry(sm.dbl, sm.buf, false)
-	sm.definedFiles = append(sm.definedFiles, entry)
+	if entry := readFileEntry(sm.dbl, sm.buf, false); entry != nil {
+		sm.definedFiles = append(sm.definedFiles, entry)
+	}
 }
 
 func prologueend(sm *StateMachine, buf *bytes.Buffer) {
diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/util/util.go
index 0fcf64f7..6ca75e1c 100644
--- a/pkg/dwarf/util/util.go
+++ b/pkg/dwarf/util/util.go
@@ -128,13 +128,13 @@ func EncodeSLEB128(out io.ByteWriter, x int64) {
 }
 
 // ParseString reads a null-terminated string from data.
-func ParseString(data *bytes.Buffer) (string, uint32) {
+func ParseString(data *bytes.Buffer) (string, error) {
 	str, err := data.ReadString(0x0)
 	if err != nil {
-		panic("Could not parse string")
+		return "", err
 	}
 
-	return str[:len(str)-1], uint32(len(str))
+	return str[:len(str)-1], nil
 }
 
 // ReadUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.
diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go
index 1e53d5cf..cd194552 100644
--- a/pkg/proc/amd64_arch.go
+++ b/pkg/proc/amd64_arch.go
@@ -39,6 +39,7 @@ func AMD64Arch(goos string) *Arch {
 		BPRegNum:                         regnum.AMD64_Rbp,
 		ContextRegNum:                    regnum.AMD64_Rdx,
 		asmRegisters:                     amd64AsmRegisters,
+		RegisterNameToDwarf:              nameToDwarfFunc(regnum.AMD64NameToDwarf),
 	}
 }
 
diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go
index 9aadc6bd..f6db9953 100644
--- a/pkg/proc/arch.go
+++ b/pkg/proc/arch.go
@@ -2,6 +2,7 @@ package proc
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/go-delve/delve/pkg/dwarf/frame"
 	"github.com/go-delve/delve/pkg/dwarf/op"
@@ -45,7 +46,8 @@ type Arch struct {
 	// register name will be returned.
 	DwarfRegisterToString func(int, *op.DwarfRegister) (string, bool, string)
 	// inhibitStepInto returns whether StepBreakpoint can be set at pc.
-	inhibitStepInto func(bi *BinaryInfo, pc uint64) bool
+	inhibitStepInto     func(bi *BinaryInfo, pc uint64) bool
+	RegisterNameToDwarf func(s string) (int, bool)
 
 	// asmRegisters maps assembly register numbers to dwarf registers.
 	asmRegisters map[int]asmRegister
@@ -131,6 +133,13 @@ func (arch *Arch) getAsmRegister(regs *op.DwarfRegisters, asmreg int) (uint64, e
 	return n, nil
 }
 
+func nameToDwarfFunc(n2d map[string]int) func(string) (int, bool) {
+	return func(name string) (int, bool) {
+		r, ok := n2d[strings.ToLower(name)]
+		return r, ok
+	}
+}
+
 // crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
 const (
 	crosscall2SPOffsetBad        = 0x8
diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go
index 30c35765..52326475 100644
--- a/pkg/proc/arm64_arch.go
+++ b/pkg/proc/arm64_arch.go
@@ -36,6 +36,7 @@ func ARM64Arch(goos string) *Arch {
 		PCRegNum:                         regnum.ARM64_PC,
 		SPRegNum:                         regnum.ARM64_SP,
 		asmRegisters:                     arm64AsmRegisters,
+		RegisterNameToDwarf:              nameToDwarfFunc(regnum.ARM64NameToDwarf),
 	}
 }
 
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index ab144854..1313a514 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -1142,6 +1142,36 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
 			return v, nil
 		}
 	}
+
+	// not a local variable, nor a global variable, try a CPU register
+	if s := validRegisterName(node.Name); s != "" {
+		if regnum, ok := scope.BinInfo.Arch.RegisterNameToDwarf(s); ok {
+			if reg := scope.Regs.Reg(uint64(regnum)); reg != nil {
+				reg.FillBytes()
+
+				var typ godwarf.Type
+				if len(reg.Bytes) <= 8 {
+					typ = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 8, Name: "uint64"}, BitSize: 64, BitOffset: 0}}
+				} else {
+					typ, err = scope.BinInfo.findType("string")
+					if err != nil {
+						return nil, err
+					}
+				}
+
+				v := newVariable(node.Name, 0, typ, scope.BinInfo, scope.Mem)
+				if v.Kind == reflect.String {
+					v.Len = int64(len(reg.Bytes) * 2)
+					v.Base = fakeAddress
+				}
+				v.Addr = fakeAddress
+				v.Flags = VariableCPURegister
+				v.reg = reg
+				return v, nil
+			}
+		}
+	}
+
 	return nil, fmt.Errorf("could not find symbol value for %s", node.Name)
 }
 
@@ -1160,6 +1190,10 @@ func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, e
 	if xv.Addr == 0 && xv.Name == "" && xv.DwarfType == nil && xv.RealType == nil {
 		return nil, fmt.Errorf("%s (type %s) is not a struct", xv.Value, xv.TypeString())
 	}
+	// Special type conversions for CPU register variables (REGNAME.int8, etc)
+	if xv.Flags&VariableCPURegister != 0 && !xv.loaded {
+		return xv.registerVariableTypeConv(node.Sel.Name)
+	}
 
 	rv, err := xv.findMethod(node.Sel.Name)
 	if err != nil {
@@ -1869,6 +1903,9 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
 	if wrong {
 		return nil, fmt.Errorf("index out of bounds")
 	}
+	if v.loaded {
+		return &v.Children[idx], nil
+	}
 	mem := v.mem
 	if v.Kind != reflect.Array {
 		mem = DereferenceMemory(mem)
@@ -1949,6 +1986,8 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
 	r.Base = base
 	r.stride = v.stride
 	r.fieldType = v.fieldType
+	r.Flags = v.Flags
+	r.reg = v.reg
 
 	return r, nil
 }
@@ -2154,3 +2193,15 @@ func (fn *Function) fakeType(bi *BinaryInfo, removeReceiver bool) (*godwarf.Func
 		// If we start using them they should be filled here.
 	}, nil
 }
+
+func validRegisterName(s string) string {
+	for len(s) > 0 && s[0] == '_' {
+		s = s[1:]
+	}
+	for i := range s {
+		if (s[i] < '0' || s[i] > '9') && (s[i] < 'A' || s[i] > 'Z') {
+			return ""
+		}
+	}
+	return s
+}
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index 4a1cab30..d35da689 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -330,8 +330,11 @@ func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs
 
 				// Workaround for darwin arm64. Apple's debugserver seems to have a problem
 				// with not selecting the correct thread in the 'g' command and the returned
-				// registers are empty / an E74 error is thrown.
-				if v[len("version:"):] == "1200" && p.bi.Arch.Name == "arm64" {
+				// registers are empty / an E74 error is thrown. This was reported to LLVM
+				// as https://bugs.llvm.org/show_bug.cgi?id=50169
+				version, err := strconv.ParseInt(v[len("version:"):], 10, 32)
+
+				if err == nil && version >= 1200 && version <= 1205 && p.bi.Arch.Name == "arm64" {
 					p.gcmdok = false
 				}
 			}
diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go
index 5a33c1cc..341b73a1 100644
--- a/pkg/proc/i386_arch.go
+++ b/pkg/proc/i386_arch.go
@@ -35,6 +35,7 @@ func I386Arch(goos string) *Arch {
 		PCRegNum:                         regnum.I386_Eip,
 		SPRegNum:                         regnum.I386_Esp,
 		asmRegisters:                     i386AsmRegisters,
+		RegisterNameToDwarf:              nameToDwarfFunc(regnum.I386NameToDwarf),
 	}
 }
 
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 3ff8e5f4..098695f5 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -11,6 +11,7 @@ import (
 	"math"
 	"reflect"
 	"sort"
+	"strconv"
 	"strings"
 	"unsafe"
 
@@ -76,6 +77,8 @@ const (
 	VariableFakeAddress
 	// VariableCPrt means the variable is a C pointer
 	VariableCPtr
+	// VariableCPURegister means this variable is a CPU register.
+	VariableCPURegister
 )
 
 // Variable represents a variable. It contains the address, name,
@@ -94,6 +97,7 @@ type Variable struct {
 
 	Value        constant.Value
 	FloatSpecial floatSpecial
+	reg          *op.DwarfRegister // contains the value of this variable if VariableCPURegister flag is set and loaded is false
 
 	Len int64
 	Cap int64
@@ -1241,9 +1245,8 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
 
 	case reflect.String:
 		var val string
-		if v.Flags&VariableCPtr == 0 {
-			val, v.Unreadable = readStringValue(DereferenceMemory(v.mem), v.Base, v.Len, cfg)
-		} else {
+		switch {
+		case v.Flags&VariableCPtr != 0:
 			var done bool
 			val, done, v.Unreadable = readCStringValue(DereferenceMemory(v.mem), v.Base, cfg)
 			if v.Unreadable == nil {
@@ -1252,6 +1255,19 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
 					v.Len++
 				}
 			}
+
+		case v.Flags&VariableCPURegister != 0:
+			val = fmt.Sprintf("%x", v.reg.Bytes)
+			s := v.Base - fakeAddress
+			if s < uint64(len(val)) {
+				val = val[s:]
+				if v.Len >= 0 && v.Len < int64(len(val)) {
+					val = val[:v.Len]
+				}
+			}
+
+		default:
+			val, v.Unreadable = readStringValue(DereferenceMemory(v.mem), v.Base, v.Len, cfg)
 		}
 		v.Value = constant.MakeString(val)
 
@@ -1287,10 +1303,13 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
 		val, v.Unreadable = readIntRaw(v.mem, v.Addr, v.RealType.(*godwarf.IntType).ByteSize)
 		v.Value = constant.MakeInt64(val)
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-		var val uint64
-		val, v.Unreadable = readUintRaw(v.mem, v.Addr, v.RealType.(*godwarf.UintType).ByteSize)
-		v.Value = constant.MakeUint64(val)
-
+		if v.Flags&VariableCPURegister != 0 {
+			v.Value = constant.MakeUint64(v.reg.Uint64Val)
+		} else {
+			var val uint64
+			val, v.Unreadable = readUintRaw(v.mem, v.Addr, v.RealType.(*godwarf.UintType).ByteSize)
+			v.Value = constant.MakeUint64(val)
+		}
 	case reflect.Bool:
 		val := make([]byte, 1)
 		_, err := v.mem.ReadMemory(val, v.Addr)
@@ -2228,6 +2247,72 @@ func (v *Variable) ConstDescr() string {
 	return ""
 }
 
+// registerVariableTypeConv implements type conversions for CPU register variables (REGNAME.int8, etc)
+func (v *Variable) registerVariableTypeConv(newtyp string) (*Variable, error) {
+	var n int = 0
+	for i := 0; i < len(v.reg.Bytes); i += n {
+		var child *Variable
+		switch newtyp {
+		case "int8":
+			child = newConstant(constant.MakeInt64(int64(int8(v.reg.Bytes[i]))), v.mem)
+			n = 1
+		case "int16":
+			child = newConstant(constant.MakeInt64(int64(int16(binary.LittleEndian.Uint16(v.reg.Bytes[i:])))), v.mem)
+			n = 2
+		case "int32":
+			child = newConstant(constant.MakeInt64(int64(int32(binary.LittleEndian.Uint32(v.reg.Bytes[i:])))), v.mem)
+			n = 4
+		case "int64":
+			child = newConstant(constant.MakeInt64(int64(binary.LittleEndian.Uint64(v.reg.Bytes[i:]))), v.mem)
+			n = 8
+		case "uint8":
+			child = newConstant(constant.MakeUint64(uint64(v.reg.Bytes[i])), v.mem)
+			n = 1
+		case "uint16":
+			child = newConstant(constant.MakeUint64(uint64(binary.LittleEndian.Uint16(v.reg.Bytes[i:]))), v.mem)
+			n = 2
+		case "uint32":
+			child = newConstant(constant.MakeUint64(uint64(binary.LittleEndian.Uint32(v.reg.Bytes[i:]))), v.mem)
+			n = 4
+		case "uint64":
+			child = newConstant(constant.MakeUint64(uint64(binary.LittleEndian.Uint64(v.reg.Bytes[i:]))), v.mem)
+			n = 8
+		case "float32":
+			a := binary.LittleEndian.Uint32(v.reg.Bytes[i:])
+			x := *(*float32)(unsafe.Pointer(&a))
+			child = newConstant(constant.MakeFloat64(float64(x)), v.mem)
+			n = 4
+		case "float64":
+			a := binary.LittleEndian.Uint64(v.reg.Bytes[i:])
+			x := *(*float64)(unsafe.Pointer(&a))
+			child = newConstant(constant.MakeFloat64(x), v.mem)
+			n = 8
+		default:
+			if n == 0 {
+				for _, pfx := range []string{"uint", "int"} {
+					if strings.HasPrefix(newtyp, pfx) {
+						n, _ = strconv.Atoi(newtyp[len(pfx):])
+						break
+					}
+				}
+				if n == 0 || popcnt(uint64(n)) != 1 {
+					return nil, fmt.Errorf("unknown CPU register type conversion to %q", newtyp)
+				}
+				n = n / 8
+			}
+			child = newConstant(constant.MakeString(fmt.Sprintf("%x", v.reg.Bytes[i:][:n])), v.mem)
+		}
+		v.Children = append(v.Children, *child)
+	}
+
+	v.loaded = true
+	v.Kind = reflect.Array
+	v.Len = int64(len(v.Children))
+	v.DwarfType = fakeArrayType(uint64(len(v.Children)), &godwarf.VoidType{CommonType: godwarf.CommonType{ByteSize: int64(n)}})
+	v.RealType = v.DwarfType
+	return v, nil
+}
+
 // popcnt is the number of bits set to 1 in x.
 // It's the same as math/bits.OnesCount64, copied here so that we can build
 // on versions of go that don't have math/bits.
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index 5b1b1143..b7c30b81 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -278,7 +278,7 @@ If regex is specified only package variables with a name matching it will be ret
 
 	regs [-a]
 
-Argument -a shows more registers.`},
+Argument -a shows more registers. Individual registers can also be displayed by 'print' and 'display'. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md.`},
 		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: `Exit the debugger.
 		
 	exit [-c]
@@ -1831,6 +1831,10 @@ func whatisCommand(t *Term, ctx callContext, args string) error {
 	if err != nil {
 		return err
 	}
+	if val.Flags&api.VariableCPURegister != 0 {
+		fmt.Println("CPU Register")
+		return nil
+	}
 	if val.Type != "" {
 		fmt.Println(val.Type)
 	}
diff --git a/service/api/types.go b/service/api/types.go
index c0094ffb..6e46cd80 100644
--- a/service/api/types.go
+++ b/service/api/types.go
@@ -239,6 +239,12 @@ const (
 	// the variable is the return value of a function call and allocated on a
 	// frame that no longer exists)
 	VariableFakeAddress
+
+	// VariableCPrt means the variable is a C pointer
+	VariableCPtr
+
+	// VariableCPURegister means this variable is a CPU register.
+	VariableCPURegister
 )
 
 // Variable describes a variable.
diff --git a/service/dap/error_ids.go b/service/dap/error_ids.go
index 03ec41e7..68a4d3de 100644
--- a/service/dap/error_ids.go
+++ b/service/dap/error_ids.go
@@ -20,6 +20,7 @@ const (
 	UnableToListGlobals        = 2007
 	UnableToLookupVariable     = 2008
 	UnableToEvaluateExpression = 2009
-	// Add more codes as we support more requests
-	DisconnectError = 5000
+
+	DebuggeeIsRunning = 4000
+	DisconnectError   = 5000
 )
diff --git a/service/dap/server.go b/service/dap/server.go
index 1eddf87e..d786a35a 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -37,7 +37,7 @@ import (
 )
 
 // Server implements a DAP server that can accept a single client for
-// a single debug session (for now). It does yet not support restarting.
+// a single debug session (for now). It does not yet support restarting.
 // That means that in addition to explicit shutdown requests,
 // program termination and failed or closed client connection
 // would also result in stopping this single-use server.
@@ -53,21 +53,41 @@ import (
 //
 // (2) Run goroutine started from Run() that serves as both
 // a listener and a client goroutine. It accepts a client connection,
-// reads, decodes and processes each request, issuing commands to the
-// underlying debugger and sending back events and responses.
-// This gorouitne sends a stop-server signal via config.DisconnecChan
-// when encounering a client connection error or responding to
-// a DAP disconnect request.
+// reads, decodes and dispatches each request from the client.
+// For synchronous requests, it issues commands to the
+// underlying debugger and sends back events and responses.
+// These requests block while the debuggee is running, so,
+// where applicable, the handlers need to check if debugging
+// state is running, so there is a need for a halt request or
+// a dummy/error response to avoid blocking.
+//
+// This is the only goroutine that sends a stop-server signal
+// via config.DisconnecChan when encountering a client connection
+// error or responding to a (synchronous) DAP disconnect request.
+// Once stop is triggered, the goroutine exits.
 //
 // TODO(polina): add another layer of per-client goroutines to support multiple clients
-// TODO(polina): make it asynchronous (i.e. launch goroutine per request)
+//
+// (3) Per-request goroutine is started for each asynchronous request
+// that resumes execution. We check if target is running already, so
+// there should be no more than one pending asynchronous request at
+// a time. This goroutine issues commands to the underlying debugger
+// and sends back events and responses. It takes a setup-done channel
+// as an argument and temporarily blocks the request loop until setup
+// for asynchronous execution is complete and targe is running.
+// Once done, it unblocks processing of parallel requests unblocks
+// (e.g. disconnecting while the program is running).
+//
+// These per-request goroutines never send a stop-server signal.
+// They block on running debugger commands that are interrupted
+// when halt is issued while stopping. At that point these goroutines
+// wrap-up and exit.
 type Server struct {
 	// config is all the information necessary to start the debugger and server.
 	config *service.Config
 	// listener is used to accept the client connection.
 	listener net.Listener
-	// stopTriggered is closed when the server is Stop()-ed. This can be used to signal
-	// to goroutines run by the server that it's time to quit.
+	// stopTriggered is closed when the server is Stop()-ed.
 	stopTriggered chan struct{}
 	// reader is used to read requests from the connection.
 	reader *bufio.Reader
@@ -86,6 +106,7 @@ type Server struct {
 	// mu synchronizes access to objects set on start-up (from run goroutine)
 	// and stopped on teardown (from main goroutine)
 	mu sync.Mutex
+
 	// conn is the accepted client connection.
 	conn net.Conn
 	// debugger is the underlying debugger service.
@@ -94,6 +115,10 @@ type Server struct {
 	binaryToRemove string
 	// noDebugProcess is set for the noDebug launch process.
 	noDebugProcess *exec.Cmd
+
+	// sendingMu synchronizes writing to net.Conn
+	// to ensure that messages do not get interleaved
+	sendingMu sync.Mutex
 }
 
 // launchAttachArgs captures arguments from launch/attach request that
@@ -124,6 +149,7 @@ var defaultArgs = launchAttachArgs{
 
 // 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?
 var DefaultLoadConfig = proc.LoadConfig{
 	FollowPointers:     true,
@@ -217,6 +243,7 @@ func (s *Server) Stop() {
 		// allowing the run goroutine to exit.
 		_ = s.conn.Close()
 	}
+
 	if s.debugger != nil {
 		killProcess := s.config.Debugger.AttachPid == 0
 		s.stopDebugSession(killProcess)
@@ -238,8 +265,6 @@ func (s *Server) Stop() {
 // The function safeguards agaist closing the channel more
 // than once and can be called multiple times. It is not thread-safe
 // and is currently only called from the run goroutine.
-// TODO(polina): lock this when we add more goroutines that could call
-// this when we support asynchronous request-response communication.
 func (s *Server) triggerServerStop() {
 	// Avoid accidentally closing the channel twice and causing a panic, when
 	// this function is called more than once. For example, we could have the
@@ -314,66 +339,137 @@ func (s *Server) serveDAPCodec() {
 	}
 }
 
+// In case a handler panics, we catch the panic to avoid crashing both
+// the server and the target. We send an error response back, but
+// in case its a dup and ignored by the client, we also log the error.
+func (s *Server) recoverPanic(request dap.Message) {
+	if ierr := recover(); ierr != nil {
+		s.log.Errorf("recovered panic: %s\n%s\n", ierr, debug.Stack())
+		s.sendInternalErrorResponse(request.GetSeq(), fmt.Sprintf("%v", ierr))
+	}
+}
+
 func (s *Server) handleRequest(request dap.Message) {
-	defer func() {
-		// In case a handler panics, we catch the panic and send an error response
-		// back to the client.
-		if ierr := recover(); ierr != nil {
-			s.log.Errorf("stacktrace from recovered panic:\n%s\n", debug.Stack())
-			s.sendInternalErrorResponse(request.GetSeq(), fmt.Sprintf("%v", ierr))
-		}
-	}()
+	defer s.recoverPanic(request)
 
 	jsonmsg, _ := json.Marshal(request)
 	s.log.Debug("[<- from client]", string(jsonmsg))
 
+	if _, ok := request.(dap.RequestMessage); !ok {
+		s.sendInternalErrorResponse(request.GetSeq(), fmt.Sprintf("Unable to process non-request %#v\n", request))
+		return
+	}
+
+	// These requests, can be handeled regardless of whether the targret is running
 	switch request := request.(type) {
-	case *dap.InitializeRequest:
-		// Required
-		s.onInitializeRequest(request)
-	case *dap.LaunchRequest:
-		// Required
-		s.onLaunchRequest(request)
-	case *dap.AttachRequest:
-		// Required
-		s.onAttachRequest(request)
 	case *dap.DisconnectRequest:
 		// Required
 		s.onDisconnectRequest(request)
+		return
+	case *dap.PauseRequest:
+		// Required
+		// TODO: implement this request in V0
+		s.onPauseRequest(request)
+		return
 	case *dap.TerminateRequest:
 		// Optional (capability ‘supportsTerminateRequest‘)
 		// TODO: implement this request in V1
 		s.onTerminateRequest(request)
+		return
 	case *dap.RestartRequest:
 		// Optional (capability ‘supportsRestartRequest’)
 		// TODO: implement this request in V1
 		s.onRestartRequest(request)
-	case *dap.SetBreakpointsRequest:
-		// Required
-		s.onSetBreakpointsRequest(request)
-	case *dap.SetFunctionBreakpointsRequest:
-		// Optional (capability ‘supportsFunctionBreakpoints’)
-		// TODO: implement this request in V1
-		s.onSetFunctionBreakpointsRequest(request)
-	case *dap.SetExceptionBreakpointsRequest:
-		// Optional (capability ‘exceptionBreakpointFilters’)
-		s.onSetExceptionBreakpointsRequest(request)
+		return
+	}
+
+	// Most requests cannot be processed while the debuggee is running.
+	// We have a couple of options for handling these without blocking
+	// the request loop indefinitely when we are in running state.
+	// --1-- Return a dummy response or an error right away.
+	// --2-- Halt execution, process the request, resume execution.
+	// TODO(polina): do this for setting breakpoints
+	// --3-- Handle such requests asynchronously and let them block until
+	// the process stops or terminates (e.g. using a channel and a single
+	// goroutine to preserve the order). This might not be appropriate
+	// for requests such as continue or step because they would skip
+	// the stop, resuming execution right away. Other requests
+	// might not be relevant anymore when the stop is finally reached, and
+	// state changed from the previous snapshot. The user might want to
+	// resume execution before the backlog of buffered requests is cleared,
+	// so we would have to either cancel them or delay processing until
+	// the next stop. In addition, the editor itself might block waiting
+	// for these requests to return. We are not aware of any requests
+	// that would benefit from this approach at this time.
+	if s.debugger != nil && s.debugger.IsRunning() {
+		switch request := request.(type) {
+		case *dap.ThreadsRequest:
+			// On start-up, the client requests the baseline of currently existing threads
+			// right away as there are a number of DAP requests that require a thread id
+			// (pause, continue, stacktrace, etc). This can happen after the program
+			// continues on entry, preventing the client from handling any pause requests
+			// from the user. We remedy this by sending back a placeholder thread id
+			// for the current goroutine.
+			response := &dap.ThreadsResponse{
+				Response: *newResponse(request.Request),
+				Body:     dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: -1, Name: "Current"}}},
+			}
+			s.send(response)
+		default:
+			r := request.(dap.RequestMessage).GetRequest()
+			s.sendErrorResponse(*r, DebuggeeIsRunning, fmt.Sprintf("Unable to process `%s`", r.Command), "debuggee is running")
+		}
+		return
+	}
+
+	// Requests below can only be handled while target is stopped.
+	// Some of them are blocking and will be handled synchronously
+	// on this goroutine while non-blocking requests will be dispatched
+	// to another goroutine. Please note that because of the running
+	// check above, there should be no more than one pending asynchronous
+	// request at a time.
+
+	// Non-blocking request handlers will signal when they are ready
+	// setting up for async execution, so more requests can be processed.
+	resumeRequestLoop := make(chan struct{})
+
+	switch request := request.(type) {
+	//--- Asynchronous requests ---
 	case *dap.ConfigurationDoneRequest:
 		// Optional (capability ‘supportsConfigurationDoneRequest’)
-		// Supported by vscode-go
-		s.onConfigurationDoneRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onConfigurationDoneRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	case *dap.ContinueRequest:
 		// Required
-		s.onContinueRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onContinueRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	case *dap.NextRequest:
 		// Required
-		s.onNextRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onNextRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	case *dap.StepInRequest:
 		// Required
-		s.onStepInRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onStepInRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	case *dap.StepOutRequest:
 		// Required
-		s.onStepOutRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onStepOutRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	case *dap.StepBackRequest:
 		// Optional (capability ‘supportsStepBack’)
 		// TODO: implement this request in V1
@@ -382,16 +478,29 @@ func (s *Server) handleRequest(request dap.Message) {
 		// Optional (capability ‘supportsStepBack’)
 		// TODO: implement this request in V1
 		s.onReverseContinueRequest(request)
-	case *dap.RestartFrameRequest:
-		// Optional (capability ’supportsRestartFrame’)
-		s.sendUnsupportedErrorResponse(request.Request)
-	case *dap.GotoRequest:
-		// Optional (capability ‘supportsGotoTargetsRequest’)
-		s.sendUnsupportedErrorResponse(request.Request)
-	case *dap.PauseRequest:
+	//--- Synchronous requests ---
+	case *dap.InitializeRequest:
 		// Required
-		// TODO: implement this request in V0
-		s.onPauseRequest(request)
+		s.onInitializeRequest(request)
+	case *dap.LaunchRequest:
+		// Required
+		s.onLaunchRequest(request)
+	case *dap.AttachRequest:
+		// Required
+		s.onAttachRequest(request)
+	case *dap.SetBreakpointsRequest:
+		// Required
+		s.onSetBreakpointsRequest(request)
+	case *dap.SetFunctionBreakpointsRequest:
+		// Optional (capability ‘supportsFunctionBreakpoints’)
+		// TODO: implement this request in V1
+		s.onSetFunctionBreakpointsRequest(request)
+	case *dap.SetExceptionBreakpointsRequest:
+		// Optional (capability ‘exceptionBreakpointFilters’)
+		s.onSetExceptionBreakpointsRequest(request)
+	case *dap.ThreadsRequest:
+		// Required
+		s.onThreadsRequest(request)
 	case *dap.StackTraceRequest:
 		// Required
 		s.onStackTraceRequest(request)
@@ -401,6 +510,9 @@ func (s *Server) handleRequest(request dap.Message) {
 	case *dap.VariablesRequest:
 		// Required
 		s.onVariablesRequest(request)
+	case *dap.EvaluateRequest:
+		// Required
+		s.onEvaluateRequest(request)
 	case *dap.SetVariableRequest:
 		// Optional (capability ‘supportsSetVariable’)
 		// Supported by vscode-go
@@ -410,20 +522,37 @@ func (s *Server) handleRequest(request dap.Message) {
 		// Optional (capability ‘supportsSetExpression’)
 		// TODO: implement this request in V1
 		s.onSetExpressionRequest(request)
+	case *dap.LoadedSourcesRequest:
+		// Optional (capability ‘supportsLoadedSourcesRequest’)
+		// TODO: implement this request in V1
+		s.onLoadedSourcesRequest(request)
+	case *dap.ReadMemoryRequest:
+		// Optional (capability ‘supportsReadMemoryRequest‘)
+		// TODO: implement this request in V1
+		s.onReadMemoryRequest(request)
+	case *dap.DisassembleRequest:
+		// Optional (capability ‘supportsDisassembleRequest’)
+		// TODO: implement this request in V1
+		s.onDisassembleRequest(request)
+	case *dap.CancelRequest:
+		// Optional (capability ‘supportsCancelRequest’)
+		// TODO: does this request make sense for delve?
+		s.onCancelRequest(request)
+	//--- Requests that we do not plan to support ---
+	case *dap.RestartFrameRequest:
+		// Optional (capability ’supportsRestartFrame’)
+		s.sendUnsupportedErrorResponse(request.Request)
+	case *dap.GotoRequest:
+		// Optional (capability ‘supportsGotoTargetsRequest’)
+		s.sendUnsupportedErrorResponse(request.Request)
 	case *dap.SourceRequest:
 		// Required
 		// This does not make sense in the context of Go as
 		// the source cannot be a string eval'ed at runtime.
 		s.sendUnsupportedErrorResponse(request.Request)
-	case *dap.ThreadsRequest:
-		// Required
-		s.onThreadsRequest(request)
 	case *dap.TerminateThreadsRequest:
 		// Optional (capability ‘supportsTerminateThreadsRequest’)
 		s.sendUnsupportedErrorResponse(request.Request)
-	case *dap.EvaluateRequest:
-		// Required
-		s.onEvaluateRequest(request)
 	case *dap.StepInTargetsRequest:
 		// Optional (capability ‘supportsStepInTargetsRequest’)
 		s.sendUnsupportedErrorResponse(request.Request)
@@ -437,28 +566,12 @@ func (s *Server) handleRequest(request dap.Message) {
 		// Optional (capability ‘supportsExceptionInfoRequest’)
 		// TODO: does this request make sense for delve?
 		s.sendUnsupportedErrorResponse(request.Request)
-	case *dap.LoadedSourcesRequest:
-		// Optional (capability ‘supportsLoadedSourcesRequest’)
-		// TODO: implement this request in V1
-		s.onLoadedSourcesRequest(request)
 	case *dap.DataBreakpointInfoRequest:
 		// Optional (capability ‘supportsDataBreakpoints’)
 		s.sendUnsupportedErrorResponse(request.Request)
 	case *dap.SetDataBreakpointsRequest:
 		// Optional (capability ‘supportsDataBreakpoints’)
 		s.sendUnsupportedErrorResponse(request.Request)
-	case *dap.ReadMemoryRequest:
-		// Optional (capability ‘supportsReadMemoryRequest‘)
-		// TODO: implement this request in V1
-		s.onReadMemoryRequest(request)
-	case *dap.DisassembleRequest:
-		// Optional (capability ‘supportsDisassembleRequest’)
-		// TODO: implement this request in V1
-		s.onDisassembleRequest(request)
-	case *dap.CancelRequest:
-		// Optional (capability ‘supportsCancelRequest’)
-		// TODO: does this request make sense for delve?
-		s.onCancelRequest(request)
 	case *dap.BreakpointLocationsRequest:
 		// Optional (capability ‘supportsBreakpointLocationsRequest’)
 		s.sendUnsupportedErrorResponse(request.Request)
@@ -477,6 +590,11 @@ func (s *Server) handleRequest(request dap.Message) {
 func (s *Server) send(message dap.Message) {
 	jsonmsg, _ := json.Marshal(message)
 	s.log.Debug("[-> to client]", string(jsonmsg))
+	// TODO(polina): consider using a channel for all the sends and to have a dedicated
+	// goroutine that reads from that channel and sends over the connection.
+	// This will avoid blocking on slow network sends.
+	s.sendingMu.Lock()
+	defer s.sendingMu.Unlock()
 	_ = dap.WriteProtocolMessage(s.conn, message)
 }
 
@@ -757,9 +875,15 @@ func (s *Server) stopDebugSession(killProcess bool) error {
 	}
 	var err error
 	var exited error
+	// Halting will stop any debugger command that's pending on another
+	// per-request goroutine, hence unblocking that goroutine to wrap-up and exit.
+	// TODO(polina): Per-request goroutine could still not be done when this one is.
+	// To avoid goroutine leaks, we can use a wait group or have the goroutine listen
+	// for a stop signal on a dedicated quit channel at suitable points (use context?).
+	// 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(err)
+		s.log.Debug("halt returned error:", err)
 		return nil
 	}
 	if err != nil {
@@ -767,10 +891,11 @@ func (s *Server) stopDebugSession(killProcess bool) error {
 		case proc.ErrProcessExited:
 			exited = err
 		default:
-			s.log.Error(err)
+			s.log.Error("halt returned error:", err)
 		}
 	} else if state.Exited {
 		exited = proc.ErrProcessExited{Pid: s.debugger.ProcessPid(), Status: state.ExitStatus}
+		s.log.Debug("halt returned state:", exited)
 	}
 	if exited != nil {
 		s.logToConsole(exited.Error())
@@ -795,12 +920,17 @@ func (s *Server) stopDebugSession(killProcess bool) error {
 	return err
 }
 
+func (s *Server) isNoDebug() bool {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return s.noDebugProcess != nil
+}
+
 func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
-	if s.noDebugProcess != nil {
+	if s.isNoDebug() {
 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "running in noDebug mode")
 		return
 	}
-	// TODO(polina): handle this while running by halting first.
 
 	if request.Arguments.Source.Path == "" {
 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "empty file path")
@@ -865,8 +995,24 @@ func (s *Server) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreak
 	s.send(&dap.SetExceptionBreakpointsResponse{Response: *newResponse(request.Request)})
 }
 
-func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) {
-	if s.debugger != nil && s.args.stopOnEntry {
+func (s *Server) asyncCommandDone(asyncSetupDone chan struct{}) {
+	if asyncSetupDone != nil {
+		select {
+		case <-asyncSetupDone:
+			// already closed
+		default:
+			close(asyncSetupDone)
+		}
+	}
+}
+
+// onConfigurationDoneRequest handles 'configurationDone' request.
+// This is an optional request enabled by capability ‘supportsConfigurationDoneRequest’.
+// It gets triggered after all the debug requests that followinitalized event,
+// so the s.debugger is guaranteed to be set.
+func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest, asyncSetupDone chan struct{}) {
+	defer s.asyncCommandDone(asyncSetupDone)
+	if s.args.stopOnEntry {
 		e := &dap.StoppedEvent{
 			Event: *newEvent("stopped"),
 			Body:  dap.StoppedEventBody{Reason: "entry", ThreadId: 1, AllThreadsStopped: true},
@@ -874,16 +1020,18 @@ func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneReques
 		s.send(e)
 	}
 	s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
-	if s.debugger != nil && !s.args.stopOnEntry {
-		s.doCommand(api.Continue)
+	if !s.args.stopOnEntry {
+		s.doCommand(api.Continue, asyncSetupDone)
 	}
 }
 
-func (s *Server) onContinueRequest(request *dap.ContinueRequest) {
+// onContinueRequest handles 'continue' request.
+// This is a mandatory request to support.
+func (s *Server) onContinueRequest(request *dap.ContinueRequest, asyncSetupDone chan struct{}) {
 	s.send(&dap.ContinueResponse{
 		Response: *newResponse(request.Request),
 		Body:     dap.ContinueResponseBody{AllThreadsContinued: true}})
-	s.doCommand(api.Continue)
+	s.doCommand(api.Continue, asyncSetupDone)
 }
 
 func fnName(loc *proc.Location) string {
@@ -893,11 +1041,15 @@ func fnName(loc *proc.Location) string {
 	return loc.Fn.Name
 }
 
+// onThreadsRequest handles 'threads' request.
+// This is a mandatory request to support.
+// It is sent in response to configurationDone response and stopped events.
 func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) {
 	if s.debugger == nil {
 		s.sendErrorResponse(request.Request, UnableToDisplayThreads, "Unable to display threads", "debugger is nil")
 		return
 	}
+
 	gs, _, err := s.debugger.Goroutines(0, 0)
 	if err != nil {
 		switch err.(type) {
@@ -997,23 +1149,23 @@ func (s *Server) onAttachRequest(request *dap.AttachRequest) {
 
 // onNextRequest handles 'next' request.
 // This is a mandatory request to support.
-func (s *Server) onNextRequest(request *dap.NextRequest) {
+func (s *Server) onNextRequest(request *dap.NextRequest, asyncSetupDone chan struct{}) {
 	s.send(&dap.NextResponse{Response: *newResponse(request.Request)})
-	s.doStepCommand(api.Next, request.Arguments.ThreadId)
+	s.doStepCommand(api.Next, request.Arguments.ThreadId, asyncSetupDone)
 }
 
 // onStepInRequest handles 'stepIn' request
 // This is a mandatory request to support.
-func (s *Server) onStepInRequest(request *dap.StepInRequest) {
+func (s *Server) onStepInRequest(request *dap.StepInRequest, asyncSetupDone chan struct{}) {
 	s.send(&dap.StepInResponse{Response: *newResponse(request.Request)})
-	s.doStepCommand(api.Step, request.Arguments.ThreadId)
+	s.doStepCommand(api.Step, request.Arguments.ThreadId, asyncSetupDone)
 }
 
 // onStepOutRequest handles 'stepOut' request
 // This is a mandatory request to support.
-func (s *Server) onStepOutRequest(request *dap.StepOutRequest) {
+func (s *Server) onStepOutRequest(request *dap.StepOutRequest, asyncSetupDone chan struct{}) {
 	s.send(&dap.StepOutResponse{Response: *newResponse(request.Request)})
-	s.doStepCommand(api.StepOut, request.Arguments.ThreadId)
+	s.doStepCommand(api.StepOut, request.Arguments.ThreadId, asyncSetupDone)
 }
 
 func stoppedGoroutineID(state *api.DebuggerState) (id int) {
@@ -1025,8 +1177,13 @@ func stoppedGoroutineID(state *api.DebuggerState) (id int) {
 	return id
 }
 
-func (s *Server) doStepCommand(command string, threadId int) {
-	// Use SwitchGoroutine to change the current goroutine.
+// doStepCommand is a wrapper around doCommand that
+// first switches selected goroutine. asyncSetupDone is
+// a channel that will be closed to signal that an
+// asynchornous command has completed setup or was interrupted
+// due to an error, so the server is ready to receive new requests.
+func (s *Server) doStepCommand(command string, threadId int, asyncSetupDone chan struct{}) {
+	defer s.asyncCommandDone(asyncSetupDone)
 	_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.SwitchGoroutine, GoroutineID: threadId}, nil)
 	if err != nil {
 		s.log.Errorf("Error switching goroutines while stepping: %v", err)
@@ -1044,7 +1201,7 @@ func (s *Server) doStepCommand(command string, threadId int) {
 		s.send(stopped)
 		return
 	}
-	s.doCommand(command)
+	s.doCommand(command, asyncSetupDone)
 }
 
 // onPauseRequest sends a not-yet-implemented error response.
@@ -1062,6 +1219,8 @@ type stackFrame struct {
 
 // onStackTraceRequest handles ‘stackTrace’ requests.
 // This is a mandatory request to support.
+// As per DAP spec, this request only gets triggered as a follow-up
+// to a successful threads request as part of the "request waterfall".
 func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) {
 	goroutineID := request.Arguments.ThreadId
 	frames, err := s.debugger.Stacktrace(goroutineID, s.args.stackTraceDepth, 0)
@@ -1101,6 +1260,9 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) {
 
 // onScopesRequest handles 'scopes' requests.
 // This is a mandatory request to support.
+// It is automatically sent as part of the threads > stacktrace > scopes > variables
+// "waterfall" to highlight the topmost frame at stops, after an evaluate request
+// for the selected scope or when a user selects different scopes in the UI.
 func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
 	sf, ok := s.stackFrameHandles.get(request.Arguments.FrameId)
 	if !ok {
@@ -1335,33 +1497,88 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 	if v.Unreadable != nil {
 		return
 	}
-	typeName := api.PrettyTypeName(v.DwarfType)
 
-	// As per https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables,
-	// some of the types might be fully or partially not loaded based on LoadConfig. For now, clearly
-	// communicate when values are not fully loaded. TODO(polina): look into loading on demand.
+	// 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.
+		value = api.ConvertVar(v).SinglelineString()
+		typeName := api.PrettyTypeName(v.DwarfType)
+		loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
+		s.log.Debugf("loading %s (type %s) with %s", qualifiedNameOrExpr, typeName, loadExpr)
+		// Make sure we can load the pointers directly, not by updating just the child
+		// This is not really necessary now because users have no way of setting FollowPointers to false.
+		config := DefaultLoadConfig
+		config.FollowPointers = true
+		vLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, loadExpr, config)
+		if err != nil {
+			value += fmt.Sprintf(" - FAILED TO LOAD: %s", err)
+		} else {
+			v.Children = vLoaded.Children
+			value = api.ConvertVar(v).SinglelineString()
+		}
+		return value
+	}
 
 	switch v.Kind {
 	case reflect.UnsafePointer:
 		// Skip child reference
 	case reflect.Ptr:
 		if v.DwarfType != nil && len(v.Children) > 0 && v.Children[0].Addr != 0 && v.Children[0].Kind != reflect.Invalid {
-			if v.Children[0].OnlyAddr {
-				value = "(not loaded) " + value
-			} else {
+			if v.Children[0].OnlyAddr { // Not loaded
+				if v.Addr == 0 {
+					// This is equvalent to the following with the cli:
+					//    (dlv) p &a7
+					//    (**main.FooBar)(0xc0000a3918)
+					//
+					// TODO(polina): what is more appropriate?
+					// Option 1: leave it unloaded because it is a special case
+					// Option 2: load it, but then we have to load the child, not the parent, unlike all others
+					// TODO(polina): see if reloadVariable can be reused here
+					cTypeName := api.PrettyTypeName(v.Children[0].DwarfType)
+					cLoadExpr := fmt.Sprintf("*(*%q)(%#x)", cTypeName, v.Children[0].Addr)
+					s.log.Debugf("loading *(%s) (type %s) with %s", qualifiedNameOrExpr, cTypeName, cLoadExpr)
+					cLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, cLoadExpr, DefaultLoadConfig)
+					if err != nil {
+						value += fmt.Sprintf(" - FAILED TO LOAD: %s", err)
+					} else {
+						cLoaded.Name = v.Children[0].Name // otherwise, this will be the pointer expression
+						v.Children = []proc.Variable{*cLoaded}
+						value = api.ConvertVar(v).SinglelineString()
+					}
+				} else {
+					value = reloadVariable(v, qualifiedNameOrExpr)
+				}
+			}
+			if !v.Children[0].OnlyAddr {
 				variablesReference = maybeCreateVariableHandle(v)
 			}
 		}
 	case reflect.Slice, reflect.Array:
 		if v.Len > int64(len(v.Children)) { // Not fully loaded
-			value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children), v.Len) + value
+			if v.Base != 0 && len(v.Children) == 0 { // Fully missing
+				value = reloadVariable(v, qualifiedNameOrExpr)
+			} else { // Partially missing (TODO)
+				value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children), v.Len) + value
+			}
 		}
 		if v.Base != 0 && len(v.Children) > 0 {
 			variablesReference = maybeCreateVariableHandle(v)
 		}
 	case reflect.Map:
 		if v.Len > int64(len(v.Children)/2) { // Not fully loaded
-			value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children)/2, v.Len) + value
+			if len(v.Children) == 0 { // Fully missing
+				value = reloadVariable(v, qualifiedNameOrExpr)
+			} else { // Partially missing (TODO)
+				value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children)/2, v.Len) + value
+			}
 		}
 		if v.Base != 0 && len(v.Children) > 0 {
 			variablesReference = maybeCreateVariableHandle(v)
@@ -1370,27 +1587,20 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 		// TODO(polina): implement auto-loading here
 	case reflect.Interface:
 		if v.Addr != 0 && len(v.Children) > 0 && v.Children[0].Kind != reflect.Invalid && v.Children[0].Addr != 0 {
-			if v.Children[0].OnlyAddr { // Not fully loaded
-				// We might be loading variables from the frame that's not topmost, so use
-				// frame-independent address-based expression, not fully-qualified name.
-				loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
-				s.log.Debugf("loading %s (type %s) with %s", qualifiedNameOrExpr, typeName, loadExpr)
-				vLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, loadExpr, DefaultLoadConfig)
-				if err != nil {
-					value += fmt.Sprintf(" - FAILED TO LOAD: %s", err)
-				} else {
-					v.Children = vLoaded.Children
-					value = api.ConvertVar(v).SinglelineString()
-				}
+			if v.Children[0].OnlyAddr { // Not loaded
+				value = reloadVariable(v, qualifiedNameOrExpr)
 			}
-			// Provide a reference to the child only if it fully loaded
 			if !v.Children[0].OnlyAddr {
 				variablesReference = maybeCreateVariableHandle(v)
 			}
 		}
 	case reflect.Struct:
 		if v.Len > int64(len(v.Children)) { // Not fully loaded
-			value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children), v.Len) + value
+			if len(v.Children) == 0 { // Fully missing
+				value = reloadVariable(v, qualifiedNameOrExpr)
+			} else { // Partially missing (TODO)
+				value = fmt.Sprintf("(loaded %d/%d) ", len(v.Children), v.Len) + value
+			}
 		}
 		if len(v.Children) > 0 {
 			variablesReference = maybeCreateVariableHandle(v)
@@ -1431,6 +1641,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 		s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", "debugger is nil", showErrorToUser)
 		return
 	}
+
 	// Default to the topmost stack frame of the current goroutine in case
 	// no frame is specified (e.g. when stopped on entry or no call stack frame is expanded)
 	goid, frame := -1, 0
@@ -1457,6 +1668,9 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
 			return
 		}
+		// TODO(polina): since call will resume execution of all goroutines,
+		// we should do this asynchronously and send a continued event to the
+		// editor, followed by a stop event when the call completes.
 		state, err := s.debugger.Command(&api.DebuggerCommand{
 			Name:                 api.Call,
 			ReturnInfoLoadConfig: api.LoadConfigFromProc(&DefaultLoadConfig),
@@ -1677,16 +1891,18 @@ func (s *Server) resetHandlesForStoppedEvent() {
 
 // doCommand runs a debugger command until it stops on
 // termination, error, breakpoint, etc, when an appropriate
-// event needs to be sent to the client.
-func (s *Server) doCommand(command string) {
-	if s.debugger == nil {
-		return
-	}
-
-	state, err := s.debugger.Command(&api.DebuggerCommand{Name: command}, nil)
+// event needs to be sent to the client. asyncSetupDone is
+// a channel that will be closed to signal that an
+// asynchornous command has completed setup or was interrupted
+// due to an error, so the server is ready to receive new requests.
+func (s *Server) doCommand(command string, asyncSetupDone chan struct{}) {
+	// TODO(polina): it appears that debugger.Command doesn't close
+	// asyncSetupDone (e.g. when having an error next while nexting).
+	// So we should always close it ourselves just in case.
+	defer s.asyncCommandDone(asyncSetupDone)
+	state, err := s.debugger.Command(&api.DebuggerCommand{Name: command}, asyncSetupDone)
 	if _, isexited := err.(proc.ErrProcessExited); isexited || err == nil && state.Exited {
-		e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
-		s.send(e)
+		s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")})
 		return
 	}
 
@@ -1697,9 +1913,15 @@ func (s *Server) doCommand(command string) {
 	if err == nil {
 		stopped.Body.ThreadId = stoppedGoroutineID(state)
 
-		switch s.debugger.StopReason() {
+		sr := s.debugger.StopReason()
+		s.log.Debugf("%q command stopped - reason %q", command, sr)
+		switch sr {
 		case proc.StopNextFinished:
 			stopped.Body.Reason = "step"
+		case proc.StopManual: // triggered by halt
+			stopped.Body.Reason = "pause"
+		case proc.StopUnknown: // can happen while stopping
+			stopped.Body.Reason = "unknown"
 		default:
 			stopped.Body.Reason = "breakpoint"
 		}
@@ -1711,7 +1933,6 @@ func (s *Server) doCommand(command string) {
 				stopped.Body.Reason = "panic"
 			}
 		}
-		s.send(stopped)
 	} else {
 		s.log.Error("runtime error: ", err)
 		stopped.Body.Reason = "runtime error"
@@ -1724,7 +1945,6 @@ func (s *Server) doCommand(command string) {
 		if err == nil {
 			stopped.Body.ThreadId = stoppedGoroutineID(state)
 		}
-		s.send(stopped)
 
 		// TODO(polina): according to the spec, the extra 'text' is supposed to show up in the UI (e.g. on hover),
 		// but so far I am unable to get this to work in vscode - see https://github.com/microsoft/vscode/issues/104475.
@@ -1739,6 +1959,11 @@ func (s *Server) doCommand(command string) {
 				Category: "stderr",
 			}})
 	}
+
+	// NOTE: If we happen to be responding to another request with an is-running
+	// error while this one completes, it is possible that the error response
+	// will arrive after this stopped event.
+	s.send(stopped)
 }
 
 func (s *Server) toClientPath(path string) string {
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 053b8787..14ecd46d 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -549,7 +549,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
 
 // State returns the current state of the debugger.
 func (d *Debugger) State(nowait bool) (*api.DebuggerState, error) {
-	if d.isRunning() && nowait {
+	if d.IsRunning() && nowait {
 		return &api.DebuggerState{Running: true}, nil
 	}
 
@@ -973,7 +973,7 @@ func (d *Debugger) setRunning(running bool) {
 	d.runningMutex.Unlock()
 }
 
-func (d *Debugger) isRunning() bool {
+func (d *Debugger) IsRunning() bool {
 	d.runningMutex.Lock()
 	defer d.runningMutex.Unlock()
 	return d.running
@@ -1711,7 +1711,7 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
 		}
 	}
 
-	if !d.isRecording() && !d.isRunning() {
+	if !d.isRecording() && !d.IsRunning() {
 		out.TargetGoVersion = d.target.BinInfo().Producer()
 	}
 
diff --git a/vendor/github.com/google/go-dap/codec.go b/vendor/github.com/google/go-dap/codec.go
index d0443b1d..f9cd799d 100644
--- a/vendor/github.com/google/go-dap/codec.go
+++ b/vendor/github.com/google/go-dap/codec.go
@@ -24,13 +24,14 @@ import (
 // DecodeProtocolMessageFieldError describes which JSON attribute
 // has an unsupported value that the decoding cannot handle.
 type DecodeProtocolMessageFieldError struct {
+	Seq        int
 	SubType    string
 	FieldName  string
 	FieldValue string
 }
 
 func (e *DecodeProtocolMessageFieldError) Error() string {
-	return fmt.Sprintf("%s %s '%s' is not supported", e.SubType, e.FieldName, e.FieldValue)
+	return fmt.Sprintf("%s %s '%s' is not supported (seq: %d)", e.SubType, e.FieldName, e.FieldValue, e.Seq)
 }
 
 // DecodeProtocolMessage parses the JSON-encoded data and returns the result of
@@ -50,7 +51,7 @@ func DecodeProtocolMessage(data []byte) (Message, error) {
 	case "event":
 		return decodeEvent(data)
 	default:
-		return nil, &DecodeProtocolMessageFieldError{"ProtocolMessage", "type", protomsg.Type}
+		return nil, &DecodeProtocolMessageFieldError{protomsg.GetSeq(), "ProtocolMessage", "type", protomsg.Type}
 	}
 }
 
@@ -69,15 +70,24 @@ func decodeRequest(data []byte) (Message, error) {
 		err := json.Unmarshal(data, requestPtr)
 		return requestPtr, err
 	}
-	return nil, &DecodeProtocolMessageFieldError{"Request", "command", r.Command}
+	return nil, &DecodeProtocolMessageFieldError{r.GetSeq(), "Request", "command", r.Command}
 }
 
 // Mapping of request commands and corresponding struct constructors that
 // can be passed to json.Unmarshal.
 var requestCtor = map[string]messageCtor{
-	"cancel":                  func() Message { return &CancelRequest{} },
-	"runInTerminal":           func() Message { return &RunInTerminalRequest{} },
-	"initialize":              func() Message { return &InitializeRequest{} },
+	"cancel":        func() Message { return &CancelRequest{} },
+	"runInTerminal": func() Message { return &RunInTerminalRequest{} },
+	"initialize": func() Message {
+		return &InitializeRequest{
+			Arguments: InitializeRequestArguments{
+				// Set the default values specified here: https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize.
+				LinesStartAt1:   true,
+				ColumnsStartAt1: true,
+				PathFormat:      "path",
+			},
+		}
+	},
 	"configurationDone":       func() Message { return &ConfigurationDoneRequest{} },
 	"launch":                  func() Message { return &LaunchRequest{} },
 	"attach":                  func() Message { return &AttachRequest{} },
@@ -136,7 +146,7 @@ func decodeResponse(data []byte) (Message, error) {
 		err := json.Unmarshal(data, responsePtr)
 		return responsePtr, err
 	}
-	return nil, &DecodeProtocolMessageFieldError{"Response", "command", r.Command}
+	return nil, &DecodeProtocolMessageFieldError{r.GetSeq(), "Response", "command", r.Command}
 }
 
 // Mapping of response commands and corresponding struct constructors that
@@ -198,7 +208,7 @@ func decodeEvent(data []byte) (Message, error) {
 		err := json.Unmarshal(data, eventPtr)
 		return eventPtr, err
 	}
-	return nil, &DecodeProtocolMessageFieldError{"Event", "event", e.Event}
+	return nil, &DecodeProtocolMessageFieldError{e.GetSeq(), "Event", "event", e.Event}
 }
 
 // Mapping of event ids and corresponding struct constructors that
diff --git a/vendor/github.com/google/go-dap/io.go b/vendor/github.com/google/go-dap/io.go
index 5b85851b..87125ef2 100644
--- a/vendor/github.com/google/go-dap/io.go
+++ b/vendor/github.com/google/go-dap/io.go
@@ -95,7 +95,7 @@ func ReadBaseMessage(r *bufio.Reader) ([]byte, error) {
 // and required:
 // 		Content-Length: [0-9]+\r\n\r\n
 // Extracts and returns the content length.
-func readContentLengthHeader(r *bufio.Reader) (contentLength int, err error) {
+func readContentLengthHeader(r *bufio.Reader) (contentLength int64, err error) {
 	// Look for <some header>\r\n\r\n
 	headerWithCr, err := r.ReadString('\r')
 	if err != nil {
@@ -115,7 +115,7 @@ func readContentLengthHeader(r *bufio.Reader) (contentLength int, err error) {
 	if len(headerAndLength) < 2 {
 		return 0, ErrHeaderNotContentLength
 	}
-	return strconv.Atoi(headerAndLength[1])
+	return strconv.ParseInt(headerAndLength[1], 10, 64)
 }
 
 // WriteProtocolMessage encodes message and writes it to w.
diff --git a/vendor/github.com/google/go-dap/schematypes.go b/vendor/github.com/google/go-dap/schematypes.go
index fcd6f61a..219159e9 100644
--- a/vendor/github.com/google/go-dap/schematypes.go
+++ b/vendor/github.com/google/go-dap/schematypes.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// DO NOT EDIT: This file is auto-generated.
+// Code generated by "cmd/gentypes/gentypes.go"; DO NOT EDIT.
 // DAP spec: https://microsoft.github.io/debug-adapter-protocol/specification
 // See cmd/gentypes/README.md for additional details.
 
@@ -166,6 +166,7 @@ type StoppedEventBody struct {
 	PreserveFocusHint bool   `json:"preserveFocusHint,omitempty"`
 	Text              string `json:"text,omitempty"`
 	AllThreadsStopped bool   `json:"allThreadsStopped,omitempty"`
+	HitBreakpointIds  []int  `json:"hitBreakpointIds,omitempty"`
 }
 
 func (e *StoppedEvent) GetEvent() *Event { return &e.Event }
@@ -374,6 +375,23 @@ type ProgressEndEventBody struct {
 
 func (e *ProgressEndEvent) GetEvent() *Event { return &e.Event }
 
+// InvalidatedEvent: This event signals that some state in the debug adapter has changed and requires that the client needs to re-render the data snapshot previously requested.
+// Debug adapters do not have to emit this event for runtime changes like stopped or thread events because in that case the client refetches the new state anyway. But the event can be used for example to refresh the UI after rendering formatting has changed in the debug adapter.
+// This event should only be sent if the debug adapter has received a value true for the 'supportsInvalidatedEvent' capability of the 'initialize' request.
+type InvalidatedEvent struct {
+	Event
+
+	Body InvalidatedEventBody `json:"body"`
+}
+
+type InvalidatedEventBody struct {
+	Areas        []InvalidatedAreas `json:"areas,omitempty"`
+	ThreadId     int                `json:"threadId,omitempty"`
+	StackFrameId int                `json:"stackFrameId,omitempty"`
+}
+
+func (e *InvalidatedEvent) GetEvent() *Event { return &e.Event }
+
 // RunInTerminalRequest: This optional request is sent from the debug adapter to the client to run a command in a terminal.
 // This is typically used to launch the debuggee in a terminal provided by the client.
 // This request should only be called if the client has passed the value true for the 'supportsRunInTerminalRequest' capability of the 'initialize' request.
@@ -427,14 +445,15 @@ type InitializeRequestArguments struct {
 	ClientName                   string `json:"clientName,omitempty"`
 	AdapterID                    string `json:"adapterID"`
 	Locale                       string `json:"locale,omitempty"`
-	LinesStartAt1                bool   `json:"linesStartAt1,omitempty"`
-	ColumnsStartAt1              bool   `json:"columnsStartAt1,omitempty"`
+	LinesStartAt1                bool   `json:"linesStartAt1"`
+	ColumnsStartAt1              bool   `json:"columnsStartAt1"`
 	PathFormat                   string `json:"pathFormat,omitempty"`
 	SupportsVariableType         bool   `json:"supportsVariableType,omitempty"`
 	SupportsVariablePaging       bool   `json:"supportsVariablePaging,omitempty"`
 	SupportsRunInTerminalRequest bool   `json:"supportsRunInTerminalRequest,omitempty"`
 	SupportsMemoryReferences     bool   `json:"supportsMemoryReferences,omitempty"`
 	SupportsProgressReporting    bool   `json:"supportsProgressReporting,omitempty"`
+	SupportsInvalidatedEvent     bool   `json:"supportsInvalidatedEvent,omitempty"`
 }
 
 // InitializeResponse: Response to 'initialize' request.
@@ -516,6 +535,7 @@ func (r *RestartRequest) GetRequest() *Request { return &r.Request }
 
 // RestartArguments: Arguments for 'restart' request.
 type RestartArguments struct {
+	Arguments interface{} `json:"arguments,omitempty"`
 }
 
 // RestartResponse: Response to 'restart' request. This is just an acknowledgement, so no body field is required.
@@ -542,6 +562,7 @@ func (r *DisconnectRequest) GetRequest() *Request { return &r.Request }
 type DisconnectArguments struct {
 	Restart           bool `json:"restart,omitempty"`
 	TerminateDebuggee bool `json:"terminateDebuggee,omitempty"`
+	SuspendDebuggee   bool `json:"suspendDebuggee,omitempty"`
 }
 
 // DisconnectResponse: Response to 'disconnect' request. This is just an acknowledgement, so no body field is required.
@@ -686,13 +707,23 @@ func (r *SetExceptionBreakpointsRequest) GetRequest() *Request { return &r.Reque
 
 // SetExceptionBreakpointsArguments: Arguments for 'setExceptionBreakpoints' request.
 type SetExceptionBreakpointsArguments struct {
-	Filters          []string           `json:"filters"`
-	ExceptionOptions []ExceptionOptions `json:"exceptionOptions,omitempty"`
+	Filters          []string                 `json:"filters"`
+	FilterOptions    []ExceptionFilterOptions `json:"filterOptions,omitempty"`
+	ExceptionOptions []ExceptionOptions       `json:"exceptionOptions,omitempty"`
 }
 
-// SetExceptionBreakpointsResponse: Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required.
+// SetExceptionBreakpointsResponse: Response to 'setExceptionBreakpoints' request.
+// The response contains an array of Breakpoint objects with information about each exception breakpoint or filter. The Breakpoint objects are in the same order as the elements of the 'filters', 'filterOptions', 'exceptionOptions' arrays given as arguments. If both 'filters' and 'filterOptions' are given, the returned array must start with 'filters' information first, followed by 'filterOptions' information.
+// The mandatory 'verified' property of a Breakpoint object signals whether the exception breakpoint or filter could be successfully created and whether the optional condition or hit count expressions are valid. In case of an error the 'message' property explains the problem. An optional 'id' property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events.
+// For backward compatibility both the 'breakpoints' array and the enclosing 'body' are optional. If these elements are missing a client will not be able to show problems for individual exception breakpoints or filters.
 type SetExceptionBreakpointsResponse struct {
 	Response
+
+	Body SetExceptionBreakpointsResponseBody `json:"body,omitempty"`
+}
+
+type SetExceptionBreakpointsResponseBody struct {
+	Breakpoints []Breakpoint `json:"breakpoints,omitempty"`
 }
 
 func (r *SetExceptionBreakpointsResponse) GetResponse() *Response { return &r.Response }
@@ -1008,7 +1039,8 @@ type PauseResponse struct {
 
 func (r *PauseResponse) GetResponse() *Response { return &r.Response }
 
-// StackTraceRequest: The request returns a stacktrace from the current execution state.
+// StackTraceRequest: The request returns a stacktrace from the current execution state of a given thread.
+// A client can request all stack frames by omitting the startFrame and levels arguments. For performance conscious clients and if the debug adapter's 'supportsDelayedStackTraceLoading' capability is true, stack frames can be retrieved in a piecemeal way with the startFrame and levels arguments. The response of the stackTrace request may contain a totalFrames property that hints at the total number of frames in the stack. If a client needs this total number upfront, it can issue a request for a single (first) frame and depending on the value of totalFrames decide how to proceed. In any case a client should be prepared to receive less frames than requested, which is an indication that the end of the stack has been reached.
 type StackTraceRequest struct {
 	Request
 
@@ -1544,6 +1576,7 @@ type Capabilities struct {
 	SupportsValueFormattingOptions     bool                         `json:"supportsValueFormattingOptions,omitempty"`
 	SupportsExceptionInfoRequest       bool                         `json:"supportsExceptionInfoRequest,omitempty"`
 	SupportTerminateDebuggee           bool                         `json:"supportTerminateDebuggee,omitempty"`
+	SupportSuspendDebuggee             bool                         `json:"supportSuspendDebuggee,omitempty"`
 	SupportsDelayedStackTraceLoading   bool                         `json:"supportsDelayedStackTraceLoading,omitempty"`
 	SupportsLoadedSourcesRequest       bool                         `json:"supportsLoadedSourcesRequest,omitempty"`
 	SupportsLogPoints                  bool                         `json:"supportsLogPoints,omitempty"`
@@ -1558,13 +1591,17 @@ type Capabilities struct {
 	SupportsClipboardContext           bool                         `json:"supportsClipboardContext,omitempty"`
 	SupportsSteppingGranularity        bool                         `json:"supportsSteppingGranularity,omitempty"`
 	SupportsInstructionBreakpoints     bool                         `json:"supportsInstructionBreakpoints,omitempty"`
+	SupportsExceptionFilterOptions     bool                         `json:"supportsExceptionFilterOptions,omitempty"`
 }
 
-// ExceptionBreakpointsFilter: An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with.
+// ExceptionBreakpointsFilter: An ExceptionBreakpointsFilter is shown in the UI as an filter option for configuring how exceptions are dealt with.
 type ExceptionBreakpointsFilter struct {
-	Filter  string `json:"filter"`
-	Label   string `json:"label"`
-	Default bool   `json:"default,omitempty"`
+	Filter               string `json:"filter"`
+	Label                string `json:"label"`
+	Description          string `json:"description,omitempty"`
+	Default              bool   `json:"default,omitempty"`
+	SupportsCondition    bool   `json:"supportsCondition,omitempty"`
+	ConditionDescription string `json:"conditionDescription,omitempty"`
 }
 
 // ErrorMessage: A structured message object. Used to return errors from requests.
@@ -1644,6 +1681,7 @@ type StackFrame struct {
 	Column                      int         `json:"column"`
 	EndLine                     int         `json:"endLine,omitempty"`
 	EndColumn                   int         `json:"endColumn,omitempty"`
+	CanRestart                  bool        `json:"canRestart,omitempty"`
 	InstructionPointerReference string      `json:"instructionPointerReference,omitempty"`
 	ModuleId                    interface{} `json:"moduleId,omitempty"`
 	PresentationHint            string      `json:"presentationHint,omitempty"`
@@ -1809,6 +1847,12 @@ type StackFrameFormat struct {
 	IncludeAll      bool `json:"includeAll,omitempty"`
 }
 
+// ExceptionFilterOptions: An ExceptionFilterOptions is used to specify an exception filter together with a condition for the setExceptionsFilter request.
+type ExceptionFilterOptions struct {
+	FilterId  string `json:"filterId"`
+	Condition string `json:"condition,omitempty"`
+}
+
 // ExceptionOptions: An ExceptionOptions assigns configuration options to a set of exceptions.
 type ExceptionOptions struct {
 	Path      []ExceptionPathSegment `json:"path,omitempty"`
@@ -1852,3 +1896,6 @@ type DisassembledInstruction struct {
 	EndLine          int    `json:"endLine,omitempty"`
 	EndColumn        int    `json:"endColumn,omitempty"`
 }
+
+// InvalidatedAreas: Logical areas that can be invalidated by the 'invalidated' event.
+type InvalidatedAreas string
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 1d945805..8de15d3d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -4,7 +4,7 @@ github.com/cosiner/argv
 github.com/cpuguy83/go-md2man/md2man
 # github.com/creack/pty v1.1.9
 github.com/creack/pty
-# github.com/google/go-dap v0.4.0
+# github.com/google/go-dap v0.5.0
 github.com/google/go-dap
 # github.com/hashicorp/golang-lru v0.5.4
 github.com/hashicorp/golang-lru/simplelru


Test patch:
diff --git a/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md
index e86207cd..59198414 100644
--- a/Documentation/usage/dlv_test.md
+++ b/Documentation/usage/dlv_test.md
@@ -10,7 +10,11 @@ Compiles a test binary with optimizations disabled and begins a new debug sessio
 The test command allows you to begin a new debug session in the context of your
 unit tests. By default Delve will debug the tests in the current directory.
 Alternatively you can specify a package name, and Delve will debug the tests in
-that package instead.
+that package instead. Double-dashes `--` can be used to pass arguments to the test program:
+
+dlv test [package] -- -test.v -other-argument
+
+See also: 'go help testflag'.
 
 ```
 dlv test [package]
diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go
index 7ffeaec0..f5988e3a 100644
--- a/_fixtures/testvariables2.go
+++ b/_fixtures/testvariables2.go
@@ -315,6 +315,7 @@ func main() {
 	zsvmap := map[string]struct{}{"testkey": struct{}{}}
 	var tm truncatedMap
 	tm.v = []map[string]astruct{m1}
+	var rettm = func() truncatedMap { return tm }
 
 	emptyslice := []string{}
 	emptymap := make(map[string]string)
@@ -362,5 +363,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, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice)
+	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)
 }
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index bd920d26..65e011ad 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -33,8 +33,12 @@ const noChildren bool = false
 var testBackend string
 
 func TestMain(m *testing.M) {
+	logOutputVal := ""
+	if _, isTeamCityTest := os.LookupEnv("TEAMCITY_VERSION"); isTeamCityTest {
+		logOutputVal = "debugger,dap"
+	}
 	var logOutput string
-	flag.StringVar(&logOutput, "log-output", "", "configures log output")
+	flag.StringVar(&logOutput, "log-output", logOutputVal, "configures log output")
 	flag.Parse()
 	logflags.Setup(logOutput != "", logOutput, "")
 	protest.DefaultTestBackend(&testBackend)
@@ -341,22 +345,51 @@ func TestAttachStopOnEntry(t *testing.T) {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Result=2", evResp)
 		}
 
-		// We cannot contiue here like in the launch case becase the program
-		// will never terminate. Since we won't receive a terminate event and
-		// there are no breakpoints to stop on, we just detach.
+		// 12 >> continue, << continue
+		client.ContinueRequest(1)
+		cResp := client.ExpectContinueResponse(t)
+		if cResp.Seq != 0 || cResp.RequestSeq != 12 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12", cResp)
+		}
 
 		// TODO(polina): once https://github.com/go-delve/delve/issues/2259 is
 		// fixed, test with kill=false.
 
-		// 12 >> disconnect, << disconnect
+		// 13 >> disconnect, << disconnect
 		client.DisconnectRequestWithKillOption(true)
-		oed := client.ExpectOutputEventDetachingKill(t)
-		if oed.Seq != 0 || oed.Body.Category != "console" {
-			t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
+
+		// Both of these scenarios are somehow possible.
+		// Even though the program has an infininte loop,
+		// it apears that a halt can cause it to terminate.
+		// Since we are in async mode while running, we might receive messages in either order.
+		msg := client.ExpectMessage(t)
+		switch m := msg.(type) {
+		case *dap.StoppedEvent:
+			if m.Seq != 0 || m.Body.Reason != "pause" { // continue is interrupted
+				t.Errorf("\ngot %#v\nwant Seq=0 Reason='pause'", m)
+			}
+			oed := client.ExpectOutputEventDetachingKill(t)
+			if oed.Seq != 0 || oed.Body.Category != "console" {
+				t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
+			}
+		case *dap.TerminatedEvent:
+			if m.Seq != 0 {
+				t.Errorf("\ngot %#v\nwant Seq=0'", m)
+			}
+			oep := client.ExpectOutputEventProcessExited(t, 0)
+			if oep.Seq != 0 || oep.Body.Category != "console" {
+				t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oep)
+			}
+			oed := client.ExpectOutputEventDetaching(t)
+			if oed.Seq != 0 || oed.Body.Category != "console" {
+				t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
+			}
+		default:
+			t.Fatalf("got %#v, want StoppedEvent or TerminatedEvent", m)
 		}
 		dResp := client.ExpectDisconnectResponse(t)
-		if dResp.Seq != 0 || dResp.RequestSeq != 12 {
-			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12", dResp)
+		if dResp.Seq != 0 || dResp.RequestSeq != 13 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=13", dResp)
 		}
 	})
 }
@@ -384,35 +417,47 @@ func TestContinueOnEntry(t *testing.T) {
 		// 5 >> configurationDone, << configurationDone
 		client.ConfigurationDoneRequest()
 		client.ExpectConfigurationDoneResponse(t)
-		// "Continue" happens behind the scenes
-
-		// For now continue is blocking and runs until a stop or
-		// termination. But once we upgrade the server to be async,
-		// a simultaneous threads request can be made while continue
-		// is running. Note that vscode-go just keeps track of the
-		// continue state and would just return a dummy response
-		// without talking to debugger if continue was in progress.
-		// TODO(polina): test this once it is possible
+		// "Continue" happens behind the scenes on another goroutine
 
-		client.ExpectTerminatedEvent(t)
+		// 6 >> threads, << threads
+		client.ThreadsRequest()
+		// Since we are in async mode while running, we might receive messages in either order.
+		for i := 0; i < 2; i++ {
+			msg := client.ExpectMessage(t)
+			switch m := msg.(type) {
+			case *dap.ThreadsResponse:
+				if m.Seq != 0 || m.RequestSeq != 6 {
+					t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6", m)
+				}
+				// Single current thread is sent when the program is running
+				// because DAP spec expects at least one thread.
+				// TODO(polina): accept empty already-terminated response here as well?
+				if len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current" {
+					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\"", m.Body.Threads)
+				}
+			case *dap.TerminatedEvent:
+			default:
+				t.Fatalf("got %#v, want ThreadsResponse or TerminatedEvent", m)
+			}
+		}
 
 		// It is possible for the program to terminate before the initial
-		// threads request is processed.
-
-		// 6 >> threads, << threads
+		// threads request is processed. And in that case, the
+		// response can be empty
+		// 7 >> threads, << threads
 		client.ThreadsRequest()
 		tResp := client.ExpectThreadsResponse(t)
-		if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 0 {
-			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=0", tResp)
+		if tResp.Seq != 0 || tResp.RequestSeq != 7 || len(tResp.Body.Threads) != 0 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7 len(Threads)=0", tResp)
 		}
 
-		// 7 >> disconnect, << disconnect
+		// 8 >> disconnect, << disconnect
 		client.DisconnectRequest()
 		client.ExpectOutputEventProcessExited(t, 0)
 		client.ExpectOutputEventDetaching(t)
 		dResp := client.ExpectDisconnectResponse(t)
-		if dResp.Seq != 0 || dResp.RequestSeq != 7 {
-			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7", dResp)
+		if dResp.Seq != 0 || dResp.RequestSeq != 8 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=8", dResp)
 		}
 	})
 }
@@ -443,19 +488,28 @@ func TestPreSetBreakpoint(t *testing.T) {
 
 		client.ConfigurationDoneRequest()
 		client.ExpectConfigurationDoneResponse(t)
-		// This triggers "continue"
-
-		// TODO(polina): add a no-op threads request
-		// with dummy response here once server becomes async
-		// to match what happens in VS Code.
+		// This triggers "continue" on a separate goroutine
 
-		stopEvent1 := client.ExpectStoppedEvent(t)
-		if stopEvent1.Body.Reason != "breakpoint" ||
-			stopEvent1.Body.ThreadId != 1 ||
-			!stopEvent1.Body.AllThreadsStopped {
-			t.Errorf("got %#v, want Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", stopEvent1)
+		client.ThreadsRequest()
+		// Since we are in async mode while running, we might receive messages in either order.
+		for i := 0; i < 2; i++ {
+			msg := client.ExpectMessage(t)
+			switch m := msg.(type) {
+			case *dap.ThreadsResponse:
+				if len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current" {
+					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\"", m.Body.Threads)
+				}
+			case *dap.StoppedEvent:
+				if m.Body.Reason != "breakpoint" || m.Body.ThreadId != 1 || !m.Body.AllThreadsStopped {
+					t.Errorf("got %#v, want Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", m)
+				}
+			default:
+				t.Fatalf("got %#v, want ThreadsResponse or StoppedEvent", m)
+			}
 		}
 
+		// Threads-StackTrace-Scopes-Variables request waterfall is
+		// triggered on stop event.
 		client.ThreadsRequest()
 		tResp := client.ExpectThreadsResponse(t)
 		if len(tResp.Body.Threads) < 2 { // 1 main + runtime
@@ -1374,7 +1428,7 @@ func TestVariablesLoading(t *testing.T) {
 						expectChildren(t, sd, "sd", 5)
 					}
 
-					// Struct fully missing due to hitting LoadConfig.MaxVariableRecurse (also tests evaluateName)
+					// Fully missing struct auto-loaded when reaching LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
 					ref = expectVarRegex(t, locals, -1, "c1", "c1", `main\.cstruct {pb: \*main\.bstruct {a: \(\*main\.astruct\)\(0x[0-9a-f]+\)}, sa: []\*main\.astruct len: 3, cap: 3, [\*\(\*main\.astruct\)\(0x[0-9a-f]+\),\*\(\*main\.astruct\)\(0x[0-9a-f]+\),\*\(\*main.astruct\)\(0x[0-9a-f]+\)]}`, hasChildren)
 					if ref > 0 {
 						client.VariablesRequest(ref)
@@ -1387,16 +1441,16 @@ func TestVariablesLoading(t *testing.T) {
 							expectChildren(t, c1sa, "c1.sa", 3)
 							ref = expectVarRegex(t, c1sa, 0, `\[0\]`, `c1\.sa\[0\]`, `\*\(\*main\.astruct\)\(0x[0-9a-f]+\)`, hasChildren)
 							if ref > 0 {
+								// Auto-loading of fully missing struc children happens here
 								client.VariablesRequest(ref)
 								c1sa0 := client.ExpectVariablesResponse(t)
 								expectChildren(t, c1sa0, "c1.sa[0]", 1)
-								// TODO(polina): there should be children here once we support auto loading
-								expectVarRegex(t, c1sa0, 0, "", `\(\*c1\.sa\[0\]\)`, `\(loaded 0/2\) \(\*main\.astruct\)\(0x[0-9a-f]+\)`, noChildren)
+								expectVarExact(t, c1sa0, 0, "", "(*c1.sa[0])", "main.astruct {A: 1, B: 2}", hasChildren)
 							}
 						}
 					}
 
-					// Struct fully missing due to hitting LoadConfig.MaxVariableRecurse (also tests evaluteName)
+					// Fully missing struct auto-loaded when hitting LoadConfig.MaxVariableRecurse (also tests evaluteName corner case)
 					ref = expectVarRegex(t, locals, -1, "aas", "aas", `\[\]main\.a len: 1, cap: 1, \[{aas: \[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]}\]`, hasChildren)
 					if ref > 0 {
 						client.VariablesRequest(ref)
@@ -1409,16 +1463,22 @@ func TestVariablesLoading(t *testing.T) {
 							expectChildren(t, aas0, "aas[0]", 1)
 							ref = expectVarRegex(t, aas0, 0, "aas", `aas\[0\]\.aas`, `\[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]`, hasChildren)
 							if ref > 0 {
+								// Auto-loading of fully missing struct children happens here
 								client.VariablesRequest(ref)
 								aas0aas := client.ExpectVariablesResponse(t)
 								expectChildren(t, aas0aas, "aas[0].aas", 1)
-								// TODO(polina): there should be a child here once we support auto loading - test for "aas[0].aas[0].aas"
-								expectVarRegex(t, aas0aas, 0, "[0]", `aas\[0\]\.aas\[0\]`, `\(loaded 0/1\) \(\*main\.a\)\(0x[0-9a-f]+\)`, noChildren)
+								ref = expectVarRegex(t, aas0aas, 0, "[0]", `aas\[0\]\.aas\[0\]`, `main\.a {aas: \[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]}`, hasChildren)
+								if ref > 0 {
+									client.VariablesRequest(ref)
+									aas0aas0 := client.ExpectVariablesResponse(t)
+									expectChildren(t, aas0aas, "aas[0].aas[0]", 1)
+									expectVarRegex(t, aas0aas0, 0, "aas", `aas\[0\]\.aas\[0\]\.aas`, `\[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]`, hasChildren)
+								}
 							}
 						}
 					}
 
-					// Map fully missing due to hitting LoadConfig.MaxVariableRecurse (also tests evaluateName)
+					// Fully missing map auto-loaded when hitting LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
 					ref = expectVarExact(t, locals, -1, "tm", "tm", "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", hasChildren)
 					if ref > 0 {
 						client.VariablesRequest(ref)
@@ -1426,13 +1486,54 @@ func TestVariablesLoading(t *testing.T) {
 						expectChildren(t, tm, "tm", 1)
 						ref = expectVarExact(t, tm, 0, "v", "tm.v", "[]map[string]main.astruct len: 1, cap: 1, [[...]]", hasChildren)
 						if ref > 0 {
+							// Auto-loading of fully missing map chidlren happens here, but they get trancated at MaxArrayValuess
 							client.VariablesRequest(ref)
 							tmV := client.ExpectVariablesResponse(t)
 							expectChildren(t, tmV, "tm.v", 1)
-							// TODO(polina): there should be children here once we support auto loading - test for "tm.v[0]["gutters"]"
-							expectVarExact(t, tmV, 0, "[0]", "tm.v[0]", "(loaded 0/66) map[string]main.astruct [...]", noChildren)
+							ref = expectVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, hasChildren)
+							if ref > 0 {
+								client.VariablesRequest(ref)
+								tmV0 := client.ExpectVariablesResponse(t)
+								expectChildren(t, tmV0, "tm.v[0]", 64)
+							}
 						}
 					}
+
+					// Auto-loading works with call return variables as well
+					protest.MustSupportFunctionCalls(t, testBackend)
+					client.EvaluateRequest("call rettm()", 1000, "repl")
+					got := client.ExpectEvaluateResponse(t)
+					ref = expectEval(t, got, "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						rv := client.ExpectVariablesResponse(t)
+						expectChildren(t, rv, "rv", 1)
+						ref = expectVarExact(t, rv, 0, "~r0", "", "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", hasChildren)
+						if ref > 0 {
+							client.VariablesRequest(ref)
+							tm := client.ExpectVariablesResponse(t)
+							expectChildren(t, tm, "tm", 1)
+							ref = expectVarExact(t, tm, 0, "v", "", "[]map[string]main.astruct len: 1, cap: 1, [[...]]", hasChildren)
+							if ref > 0 {
+								// Auto-loading of fully missing map chidlren happens here, but they get trancated at MaxArrayValuess
+								client.VariablesRequest(ref)
+								tmV := client.ExpectVariablesResponse(t)
+								expectChildren(t, tmV, "tm.v", 1)
+								// TODO(polina): this evaluate name is not usable - it should be empty
+								ref = expectVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, hasChildren)
+								if ref > 0 {
+									client.VariablesRequest(ref)
+									tmV0 := client.ExpectVariablesResponse(t)
+									expectChildren(t, tmV0, "tm.v[0]", 64)
+								}
+							}
+						}
+					}
+
+					// TODO(polina): need fully missing array/slice test case
+
+					// Zero slices, structs and maps are not treated as fully missing
+					// See zsvar, zsslice,, emptyslice, emptymap, a0
 				},
 				disconnect: true,
 			}})
@@ -1491,9 +1592,26 @@ func TestVariablesLoading(t *testing.T) {
 							}
 						}
 
-						// Pointer value not loaded with LoadConfig.FollowPointers=false
-						expectVarRegex(t, locals, -1, "a7", "a7", `\(not loaded\) \(\*main\.FooBar\)\(0x[0-9a-f]+\)`, noChildren)
+						// Pointer values loaded even with LoadConfig.FollowPointers=false
+
+						expectVarExact(t, locals, -1, "a7", "a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", hasChildren)
+
+						// Auto-loading works on results of evaluate expressions as well
+						client.EvaluateRequest("a7", frame, "repl")
+						expectEval(t, client.ExpectEvaluateResponse(t), "*main.FooBar {Baz: 5, Bur: \"strum\"}", hasChildren)
+
+						client.EvaluateRequest("&a7", frame, "repl")
+						pa7 := client.ExpectEvaluateResponse(t)
+						ref = expectEvalRegex(t, pa7, `\*\(\*main\.FooBar\)\(0x[0-9a-f]+\)`, hasChildren)
+						if ref > 0 {
+							client.VariablesRequest(ref)
+							a7 := client.ExpectVariablesResponse(t)
+							expectVarExact(t, a7, 0, "a7", "(*(&a7))", "*main.FooBar {Baz: 5, Bur: \"strum\"}", hasChildren)
+						}
 					}
+
+					// Frame-independent loading expressions allow us to auto-load
+					// variables in any frame, not just topmost.
 					loadvars(1000 /*first topmost frame*/)
 					// step into another function
 					client.StepInRequest(1)
@@ -1938,7 +2056,14 @@ func TestEvaluateRequest(t *testing.T) {
 					// Type casts of integer constants into any pointer type and vice versa
 					client.EvaluateRequest("(*int)(2)", 1000, "this context will be ignored")
 					got = client.ExpectEvaluateResponse(t)
-					expectEval(t, got, "(not loaded) (*int)(0x2)", noChildren)
+					ref = expectEvalRegex(t, got, `\*\(unreadable .+\)`, hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						val := client.ExpectVariablesResponse(t)
+						expectChildren(t, val, "*(*int)(2)", 1)
+						expectVarRegex(t, val, 0, "^$", `\(\*\(\(\*int\)\(2\)\)\)`, `\(unreadable .+\)`, noChildren)
+						validateEvaluateName(t, client, val, 0)
+					}
 
 					// Type casts between string, []byte and []rune
 					client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
@@ -2415,14 +2540,14 @@ func TestBadAccess(t *testing.T) {
 
 					expectStoppedOnError := func(errorPrefix string) {
 						t.Helper()
-						se := client.ExpectStoppedEvent(t)
-						if se.Body.ThreadId != 1 || se.Body.Reason != "runtime error" || !strings.HasPrefix(se.Body.Text, errorPrefix) {
-							t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"runtime error\" Text=\"%s\"", se, errorPrefix)
-						}
 						oe := client.ExpectOutputEvent(t)
 						if oe.Body.Category != "stderr" || !strings.HasPrefix(oe.Body.Output, "ERROR: "+errorPrefix) {
 							t.Errorf("\ngot  %#v\nwant Category=\"stderr\" Output=\"%s ...\"", oe, errorPrefix)
 						}
+						se := client.ExpectStoppedEvent(t)
+						if se.Body.ThreadId != 1 || se.Body.Reason != "runtime error" || !strings.HasPrefix(se.Body.Text, errorPrefix) {
+							t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"runtime error\" Text=\"%s\"", se, errorPrefix)
+						}
 					}
 
 					client.ContinueRequest(1)
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index ad36d42e..cda28c71 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1528,15 +1528,18 @@ func TestClientServer_FpRegisters(t *testing.T) {
 		{"XMM12", "…[ZMM12hh] 0x3ff66666666666663ff4cccccccccccd"},
 	}
 	protest.AllowRecording(t)
-	withTestClient2("fputest/", t, func(c service.Client) {
+	withTestClient2Extended("fputest/", t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) {
+		if testBackend == "rr" {
+			_, err := c.CreateBreakpoint(&api.Breakpoint{File: filepath.Join(fixture.BuildDir, "fputest.go"), Line: 27})
+			assertNoError(err, t, "CreateBreakpoint()")
+		}
+
 		state := <-c.Continue()
 		t.Logf("state after continue: %#v", state)
 
+		scope := api.EvalScope{GoroutineID: -1}
+
 		boolvar := func(name string) bool {
-			scope := api.EvalScope{GoroutineID: -1}
-			if testBackend == "rr" {
-				scope.Frame = 2
-			}
 			v, err := c.EvalVariable(scope, name, normalLoadConfig)
 			if err != nil {
 				t.Fatalf("could not read %s variable", name)
@@ -1557,8 +1560,6 @@ func TestClientServer_FpRegisters(t *testing.T) {
 		regs, err := c.ListThreadRegisters(0, true)
 		assertNoError(err, t, "ListThreadRegisters()")
 
-		t.Logf("%s", regs.String())
-
 		for _, regtest := range regtests {
 			if regtest.name == "XMM11" && !avx2 {
 				continue
@@ -1585,6 +1586,28 @@ func TestClientServer_FpRegisters(t *testing.T) {
 				t.Fatalf("register %s not found: %v", regtest.name, regs)
 			}
 		}
+
+		// Test register expressions
+
+		for _, tc := range []struct{ expr, tgt string }{
+			{"XMM1[:32]", `"cdccccccccccf43f666666666666f63f"`},
+			{"_XMM1[:32]", `"cdccccccccccf43f666666666666f63f"`},
+			{"__XMM1[:32]", `"cdccccccccccf43f666666666666f63f"`},
+			{"XMM1.int8[0]", `-51`},
+			{"XMM1.uint16[0]", `52429`},
+			{"XMM1.float32[0]", `-107374184`},
+			{"XMM1.float64[0]", `1.3`},
+		} {
+			v, err := c.EvalVariable(scope, tc.expr, normalLoadConfig)
+			if err != nil {
+				t.Fatalf("could not evalue expression %s: %v", tc.expr, err)
+			}
+			out := v.SinglelineString()
+
+			if out != tc.tgt {
+				t.Fatalf("for %q expected %q got %q\n", tc.expr, tc.tgt, out)
+			}
+		}
 	})
 }