Repository: go-delve/delve
Pull request #2379 (2021-03-10 18:03:08)

service/dap: add substitutePath configuration
Similar to substitute-path configuration in the dlv cli, substitutePath
in dap allows users to specify path mappings that are applied to the
source files in stacktrace and breakpoint requests.

Updates #2203


suzmue: cc @polinasok 
polinasok: Following up on our offline discussion:
- let's add logging every time a mapping is applied
- let's clarify in tests/comments (and eventually user-facing documentation) how relative paths are treated
suzmue: > Why would we fail instead of ignoring an optional argument that's poorly configured? And if you think it is better to fail, should we do the same with other arguments? Or maybe continue while logging a warning?

I was following the same pattern from the other arguments where a malformed argument causes an error.  I think most of the arguments are checked and then sendErrorResponses like this.
polinasok: > > Why would we fail instead of ignoring an optional argument that's poorly configured? And if you think it is better to fail, should we do the same with other arguments? Or maybe continue while logging a warning?
> 
> I was following the same pattern from the other arguments where a malformed argument causes an error. I think most of the arguments are checked and then sendErrorResponses like this.

There are actually two different patterns in the existing code for optional attributes. This was probably not the best choice on my part, so I was curious about your opinion about the overall discrepancy.

1. onLaunchRequest check if the supplied value is ill-formatted, and if it is, fails with an error.
Affected attributes: "buildFlags" (compound type, no default), "args" (compound type, no default).
2. setLaunchAttachArgs checks if the supplied value is ill-formatted and if it is, just ignores it falling back to the default.
Affected attributes: "stopOnEntry" (scalar type, default false), "stackTraceDepth" (scalar type, default 50), "showGlobalVariables" (scalar type, default false)

With your change setLaunchAttachArgs now parses 4 args, but detects and reports errors only for 1. That's why I asked if you think that maybe the rest of the args in that same helper should also trigger errors if they have the wrong type (not necessarily in this PR).

suzmue: > > > Why would we fail instead of ignoring an optional argument that's poorly configured? And if you think it is better to fail, should we do the same with other arguments? Or maybe continue while logging a warning?
> > 
> > 
> > I was following the same pattern from the other arguments where a malformed argument causes an error. I think most of the arguments are checked and then sendErrorResponses like this.
> 
> There are actually two different patterns in the existing code for optional attributes. This was probably not the best choice on my part, so I was curious about your opinion about the overall discrepancy.
> 
> 1. onLaunchRequest check if the supplied value is ill-formatted, and if it is, fails with an error.
>    Affected attributes: "buildFlags" (compound type, no default), "args" (compound type, no default).
> 2. setLaunchAttachArgs checks if the supplied value is ill-formatted and if it is, just ignores it falling back to the default.
>    Affected attributes: "stopOnEntry" (scalar type, default false), "stackTraceDepth" (scalar type, default 50), "showGlobalVariables" (scalar type, default false)
> 
> With your change setLaunchAttachArgs now parses 4 args, but detects and reports errors only for 1. That's why I asked if you think that maybe the rest of the args in that same helper should also trigger errors if they have the wrong type (not necessarily in this PR).

I think they probably should also have errors, so that we are not silently ignoring them. Will update in another PR.

Patch:
diff --git a/Documentation/cli/getting_started.md b/Documentation/cli/getting_started.md
index 23a9d96c..504a287e 100644
--- a/Documentation/cli/getting_started.md
+++ b/Documentation/cli/getting_started.md
@@ -25,7 +25,7 @@ For example given this project layout:
 │       └── bar_test.go
 ```
 
-If you are in the directory `github.com/me/foo/cmd/foo` you can simple run `dlv debug`
+If you are in the directory `github.com/me/foo/cmd/foo` you can simply run `dlv debug`
 from the command line. From anywhere else, say the project root, you can simply
 provide the package: `dlv debug github.com/me/foo/cmd/foo`. To pass flags to your program 
 separate them with `--`: `dlv debug github.com/me/foo/cmd/foo -- -arg1 value`.
@@ -86,6 +86,6 @@ As you can see, we began debugging the test binary, found our test function via
 the `funcs` command which takes a regexp to filter the list of functions, set a
 breakpoint and then continued execution until we hit that breakpoint.
 
-For more information on subcommands you can use type `dlv help`, and once in a
+For more information on subcommands you can use, type `dlv help`, and once in a
 debug session you can see all of the commands available to you by typing `help`
 at any time.
diff --git a/pkg/dwarf/godwarf/type.go b/pkg/dwarf/godwarf/type.go
index c9605ec2..15547d89 100644
--- a/pkg/dwarf/godwarf/type.go
+++ b/pkg/dwarf/godwarf/type.go
@@ -1084,13 +1084,3 @@ func zeroArray(t Type) {
 		t = at.Type
 	}
 }
-
-func resolveTypedef(typ Type) Type {
-	for {
-		if tt, ok := typ.(*TypedefType); ok {
-			typ = tt.Type
-		} else {
-			return typ
-		}
-	}
-}
diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go
index 7626f58f..ddc83410 100644
--- a/pkg/dwarf/line/state_machine.go
+++ b/pkg/dwarf/line/state_machine.go
@@ -49,14 +49,6 @@ type StateMachine struct {
 	ptrSize     int
 }
 
-type opcodeKind uint8
-
-const (
-	specialOpcode opcodeKind = iota
-	standardOpcode
-	extendedOpcode
-)
-
 type opcodefn func(*StateMachine, *bytes.Buffer)
 
 // Special opcodes
diff --git a/pkg/dwarf/util/buf.go b/pkg/dwarf/util/buf.go
index 52349fe0..d7b1ea31 100644
--- a/pkg/dwarf/util/buf.go
+++ b/pkg/dwarf/util/buf.go
@@ -53,14 +53,6 @@ func MakeBuf(d *dwarf.Data, format dataFormat, name string, off dwarf.Offset, da
 	return buf{d, format, name, off, data, nil}
 }
 
-func (b *buf) slice(length int) buf {
-	n := *b
-	data := b.data
-	b.skip(length) // Will validate length.
-	n.data = data[:length]
-	return n
-}
-
 func (b *buf) Uint8() uint8 {
 	if len(b.data) < 1 {
 		b.error("underflow")
@@ -72,34 +64,6 @@ func (b *buf) Uint8() uint8 {
 	return val
 }
 
-func (b *buf) bytes(n int) []byte {
-	if len(b.data) < n {
-		b.error("underflow")
-		return nil
-	}
-	data := b.data[0:n]
-	b.data = b.data[n:]
-	b.off += dwarf.Offset(n)
-	return data
-}
-
-func (b *buf) skip(n int) { b.bytes(n) }
-
-// string returns the NUL-terminated (C-like) string at the start of the buffer.
-// The terminal NUL is discarded.
-func (b *buf) string() string {
-	for i := 0; i < len(b.data); i++ {
-		if b.data[i] == 0 {
-			s := string(b.data[0:i])
-			b.data = b.data[i+1:]
-			b.off += dwarf.Offset(i + 1)
-			return s
-		}
-	}
-	b.error("underflow")
-	return ""
-}
-
 // Read a varint, which is 7 bits per byte, little endian.
 // the 0x80 bit means read another byte.
 func (b *buf) Varint() (c uint64, bits uint) {
diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go
index 7952ab04..8196ef96 100644
--- a/pkg/proc/arm64_arch.go
+++ b/pkg/proc/arm64_arch.go
@@ -129,7 +129,6 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 
 const arm64cgocallSPOffsetSaveSlot = 0x8
 const prevG0schedSPOffsetSaveSlot = 0x10
-const spAlign = 16
 
 func arm64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool {
 	if it.frame.Current.Fn != nil {
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index fde4cb45..87c8559e 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -69,9 +69,6 @@ type BinaryInfo struct {
 
 	lastModified time.Time // Time the executable of this process was last modified
 
-	closer         io.Closer
-	sepDebugCloser io.Closer
-
 	// PackageMap maps package names to package paths, needed to lookup types inside DWARF info.
 	// On Go1.12 this mapping is determined by using the last element of a package path, for example:
 	//   github.com/go-delve/delve
@@ -1402,23 +1399,6 @@ func (bi *BinaryInfo) parseDebugFramePE(image *Image, exe *pe.File, debugInfoByt
 	bi.parseDebugFrameGeneral(image, debugFrameBytes, ".debug_frame", err, nil, 0, "", frame.DwarfEndian(debugInfoBytes))
 }
 
-// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
-func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
-	for _, s := range f.Symbols {
-		if s.Name != name {
-			continue
-		}
-		if s.SectionNumber <= 0 {
-			return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
-		}
-		if len(f.Sections) < int(s.SectionNumber) {
-			return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
-		}
-		return s, nil
-	}
-	return nil, fmt.Errorf("no %s symbol found", name)
-}
-
 // MACH-O ////////////////////////////////////////////////////////////
 
 // loadBinaryInfoMacho specifically loads information from a Mach-O binary.
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index 026ea39d..d50f3297 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -50,13 +50,11 @@ var (
 	errFuncCallUnsupported        = errors.New("function calls not supported by this version of Go")
 	errFuncCallUnsupportedBackend = errors.New("backend does not support function calls")
 	errFuncCallInProgress         = errors.New("cannot call function while another function call is already in progress")
-	errNotACallExpr               = errors.New("not a function call")
 	errNoGoroutine                = errors.New("no goroutine selected")
 	errGoroutineNotRunning        = errors.New("selected goroutine not running")
 	errNotEnoughStack             = errors.New("not enough stack space")
 	errTooManyArguments           = errors.New("too many arguments")
 	errNotEnoughArguments         = errors.New("not enough arguments")
-	errNoAddrUnsupported          = errors.New("arguments to a function call must have an address")
 	errNotAGoFunction             = errors.New("not a Go function")
 	errFuncCallNotAllowed         = errors.New("function calls not allowed without using 'call'")
 	errFuncCallNotAllowedStrAlloc = errors.New("literal string can not be allocated because function calls are not allowed without using 'call'")
@@ -389,7 +387,7 @@ type fncallPanicErr struct {
 }
 
 func (err fncallPanicErr) Error() string {
-	return fmt.Sprintf("panic calling a function")
+	return "panic calling a function"
 }
 
 func fncallLog(fmtstr string, args ...interface{}) {
@@ -890,13 +888,6 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64
 	return nil
 }
 
-func (fncall *functionCallState) returnValues() []*Variable {
-	if fncall.panicvar != nil {
-		return []*Variable{fncall.panicvar}
-	}
-	return fncall.retvars
-}
-
 // allocString allocates spaces for the contents of v if it needs to be allocated
 func allocString(scope *EvalScope, v *Variable) error {
 	if v.Base != 0 || v.Len == 0 {
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index 71c15b14..12d550f6 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -730,7 +730,7 @@ func (p *gdbProcess) Valid() (bool, error) {
 		return false, proc.ErrProcessDetached
 	}
 	if p.exited {
-		return false, &proc.ErrProcessExited{Pid: p.Pid()}
+		return false, proc.ErrProcessExited{Pid: p.Pid()}
 	}
 	return true, nil
 }
@@ -780,7 +780,7 @@ const (
 // a breakpoint is hit or signal is received.
 func (p *gdbProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
 	if p.exited {
-		return nil, proc.StopExited, &proc.ErrProcessExited{Pid: p.conn.pid}
+		return nil, proc.StopExited, proc.ErrProcessExited{Pid: p.conn.pid}
 	}
 
 	if p.conn.direction == proc.Forward {
diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go
index 511b2772..1e0d463f 100644
--- a/pkg/proc/native/proc.go
+++ b/pkg/proc/native/proc.go
@@ -140,7 +140,7 @@ func (dbp *nativeProcess) Valid() (bool, error) {
 		return false, proc.ErrProcessDetached
 	}
 	if dbp.exited {
-		return false, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return false, proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 	return true, nil
 }
@@ -185,7 +185,7 @@ func (dbp *nativeProcess) Breakpoints() *proc.BreakpointMap {
 // sends SIGSTOP to all threads.
 func (dbp *nativeProcess) RequestManualStop() error {
 	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 	dbp.stopMu.Lock()
 	defer dbp.stopMu.Unlock()
@@ -222,7 +222,7 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
 // This could be the result of a breakpoint or signal.
 func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
 	if dbp.exited {
-		return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
 	for {
diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go
index f44aa312..1e533ef9 100644
--- a/pkg/proc/native/proc_darwin.go
+++ b/pkg/proc/native/proc_darwin.go
@@ -424,7 +424,7 @@ func (dbp *nativeProcess) resume() error {
 // stop stops all running threads and sets breakpoints
 func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 	for _, th := range dbp.threads {
 		if !th.Stopped() {
diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go
index a2a3ab16..26879000 100644
--- a/pkg/proc/native/proc_freebsd.go
+++ b/pkg/proc/native/proc_freebsd.go
@@ -349,7 +349,7 @@ func (dbp *nativeProcess) resume() error {
 // stop stops all running threads and sets breakpoints
 func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 	// set breakpoints on all threads
 	for _, th := range dbp.threads {
diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go
index 3bed3a9a..a4376891 100644
--- a/pkg/proc/native/proc_linux.go
+++ b/pkg/proc/native/proc_linux.go
@@ -505,7 +505,7 @@ func (dbp *nativeProcess) resume() error {
 // stop stops all running threads and sets breakpoints
 func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
 	for _, th := range dbp.threads {
diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go
index dbad2370..a4ccddd4 100644
--- a/pkg/proc/native/proc_windows.go
+++ b/pkg/proc/native/proc_windows.go
@@ -414,7 +414,7 @@ func (dbp *nativeProcess) resume() error {
 // stop stops all running threads threads and sets breakpoints
 func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
 	if dbp.exited {
-		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
 	dbp.os.running = false
diff --git a/pkg/proc/native/threads_linux.go b/pkg/proc/native/threads_linux.go
index d0e978c0..4c9f4a55 100644
--- a/pkg/proc/native/threads_linux.go
+++ b/pkg/proc/native/threads_linux.go
@@ -14,7 +14,6 @@ type waitStatus sys.WaitStatus
 // process details.
 type osSpecificDetails struct {
 	delayedSignal int
-	registers     sys.PtraceRegs
 	running       bool
 	setbp         bool
 }
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index 4673613b..9bc0e59c 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -359,3 +359,8 @@ func (t *Target) createFatalThrowBreakpoint() {
 func (t *Target) CurrentThread() Thread {
 	return t.currentThread
 }
+
+// SetNextBreakpointID sets the breakpoint ID of the next breakpoint
+func (t *Target) SetNextBreakpointID(id int) {
+	t.Breakpoints().breakpointIDCounter = id
+}
diff --git a/pkg/terminal/starbind/starlark.go b/pkg/terminal/starbind/starlark.go
index 8c441480..f37e01a7 100644
--- a/pkg/terminal/starbind/starlark.go
+++ b/pkg/terminal/starbind/starlark.go
@@ -266,19 +266,6 @@ func (env *Env) callMain(thread *starlark.Thread, globals starlark.StringDict, m
 	return starlark.Call(thread, mainfn, argtuple, nil)
 }
 
-type argument struct {
-	name         string
-	defaultValue defaultValue
-}
-
-type defaultValue uint8
-
-const (
-	defaultNone = iota
-	defaultScope
-	defaultLoadConfig
-)
-
 func isCancelled(thread *starlark.Thread) error {
 	if ctx, ok := thread.Local(dlvContextName).(context.Context); ok {
 		select {
diff --git a/service/dap/error_ids.go b/service/dap/error_ids.go
index 8b1ac928..03ec41e7 100644
--- a/service/dap/error_ids.go
+++ b/service/dap/error_ids.go
@@ -21,4 +21,5 @@ const (
 	UnableToLookupVariable     = 2008
 	UnableToEvaluateExpression = 2009
 	// Add more codes as we support more requests
+	DisconnectError = 5000
 )
diff --git a/service/dap/server.go b/service/dap/server.go
index 39e74a82..a77f913f 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -25,6 +25,7 @@ import (
 	"sync"
 
 	"github.com/go-delve/delve/pkg/gobuild"
+	"github.com/go-delve/delve/pkg/locspec"
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/service"
@@ -59,8 +60,6 @@ type Server struct {
 	debugger *debugger.Debugger
 	// log is used for structured logging.
 	log *logrus.Entry
-	// binaryToRemove is the compiled binary to be removed on disconnect.
-	binaryToRemove string
 	// stackFrameHandles maps frames of each goroutine to unique ids across all goroutines.
 	// Reset at every stop.
 	stackFrameHandles *handlesMap
@@ -72,7 +71,8 @@ type Server struct {
 	args launchAttachArgs
 
 	mu sync.Mutex
-
+	// binaryToRemove is the temp compiled binary to be removed on disconnect (if any).
+	binaryToRemove string
 	// noDebugProcess is set for the noDebug launch process.
 	noDebugProcess *exec.Cmd
 }
@@ -86,13 +86,21 @@ type launchAttachArgs struct {
 	stackTraceDepth int
 	// showGlobalVariables indicates if global package variables should be loaded.
 	showGlobalVariables bool
+	// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
+	// These must be directory paths.
+	substitutePathClientToServer [][2]string
+	// substitutePathServerToClient indicates rules for converting file paths between debugger and client.
+	// These must be directory paths.
+	substitutePathServerToClient [][2]string
 }
 
 // defaultArgs borrows the defaults for the arguments from the original vscode-go adapter.
 var defaultArgs = launchAttachArgs{
-	stopOnEntry:         false,
-	stackTraceDepth:     50,
-	showGlobalVariables: false,
+	stopOnEntry:                  false,
+	stackTraceDepth:              50,
+	showGlobalVariables:          false,
+	substitutePathClientToServer: [][2]string{},
+	substitutePathServerToClient: [][2]string{},
 }
 
 // DefaultLoadConfig controls how variables are loaded from the target's memory, borrowing the
@@ -127,7 +135,7 @@ func NewServer(config *service.Config) *Server {
 
 // If user-specified options are provided via Launch/AttachRequest,
 // we override the defaults for optional args.
-func (s *Server) setLaunchAttachArgs(request dap.LaunchAttachRequest) {
+func (s *Server) setLaunchAttachArgs(request dap.LaunchAttachRequest) error {
 	stop, ok := request.GetArguments()["stopOnEntry"].(bool)
 	if ok {
 		s.args.stopOnEntry = stop
@@ -140,6 +148,35 @@ func (s *Server) setLaunchAttachArgs(request dap.LaunchAttachRequest) {
 	if ok {
 		s.args.showGlobalVariables = globals
 	}
+	paths, ok := request.GetArguments()["substitutePath"]
+	if ok {
+		typeMismatchError := fmt.Errorf("'substitutePath' attribute '%v' in debug configuration is not a []{'from': string, 'to': string}", paths)
+		pathsParsed, ok := paths.([]interface{})
+		if !ok {
+			return typeMismatchError
+		}
+		clientToServer := make([][2]string, 0, len(pathsParsed))
+		serverToClient := make([][2]string, 0, len(pathsParsed))
+		for _, arg := range pathsParsed {
+			pathMapping, ok := arg.(map[string]interface{})
+			if !ok {
+				return typeMismatchError
+			}
+			from, ok := pathMapping["from"].(string)
+			if !ok {
+				return typeMismatchError
+			}
+			to, ok := pathMapping["to"].(string)
+			if !ok {
+				return typeMismatchError
+			}
+			clientToServer = append(clientToServer, [2]string{from, to})
+			serverToClient = append(serverToClient, [2]string{to, from})
+		}
+		s.args.substitutePathClientToServer = clientToServer
+		s.args.substitutePathServerToClient = serverToClient
+	}
+	return nil
 }
 
 // Stop stops the DAP debugger service, closes the listener and the client
@@ -147,6 +184,7 @@ func (s *Server) setLaunchAttachArgs(request dap.LaunchAttachRequest) {
 // process if it was launched by it. This method mustn't be called more than
 // once.
 func (s *Server) Stop() {
+	s.log.Debug("DAP server stopping...")
 	_ = s.listener.Close()
 	close(s.stopChan)
 	if s.conn != nil {
@@ -160,7 +198,7 @@ func (s *Server) Stop() {
 		kill := s.config.Debugger.AttachPid == 0
 		if err := s.debugger.Detach(kill); err != nil {
 			switch err.(type) {
-			case *proc.ErrProcessExited:
+			case proc.ErrProcessExited:
 				s.log.Debug(err)
 			default:
 				s.log.Error(err)
@@ -169,6 +207,14 @@ func (s *Server) Stop() {
 	} else {
 		s.stopNoDebugProcess()
 	}
+	// The binary is no longer in use by the debugger. It is safe to remove it.
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if s.binaryToRemove != "" {
+		gobuild.Remove(s.binaryToRemove)
+		s.binaryToRemove = ""
+	}
+	s.log.Debug("DAP server stopped")
 }
 
 // signalDisconnect closes config.DisconnectChan if not nil, which
@@ -195,10 +241,9 @@ func (s *Server) signalDisconnect() {
 		close(s.config.DisconnectChan)
 		s.config.DisconnectChan = nil
 	}
-	if s.binaryToRemove != "" {
-		gobuild.Remove(s.binaryToRemove)
-		s.binaryToRemove = ""
-	}
+	// There should be no logic here after the stop-server
+	// signal that might cause everything to shutdown before this
+	// logic gets executed.
 }
 
 // Run launches a new goroutine where it accepts a client connection
@@ -421,6 +466,15 @@ func (s *Server) send(message dap.Message) {
 	_ = dap.WriteProtocolMessage(s.conn, message)
 }
 
+func (s *Server) logToConsole(msg string) {
+	s.send(&dap.OutputEvent{
+		Event: *newEvent("output"),
+		Body: dap.OutputEventBody{
+			Output:   msg + "\n",
+			Category: "console",
+		}})
+}
+
 func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
 	// TODO(polina): Respond with an error if debug session is in progress?
 	response := &dap.InitializeResponse{Response: *newResponse(request.Request)}
@@ -513,10 +567,18 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 			return
 		}
 		program = debugbinary
+		s.mu.Lock()
 		s.binaryToRemove = debugbinary
+		s.mu.Unlock()
 	}
 
-	s.setLaunchAttachArgs(request)
+	err := s.setLaunchAttachArgs(request)
+	if err != nil {
+		s.sendErrorResponse(request.Request,
+			FailedToLaunch, "Failed to launch",
+			err.Error())
+		return
+	}
 
 	var targetArgs []string
 	args, ok := request.Arguments["args"]
@@ -568,9 +630,8 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 
 		// Then, block until the program terminates or is stopped.
 		if err := cmd.Wait(); err != nil {
-			s.log.Debugf("program exited: %v", err)
+			s.log.Debugf("program exited with error: %v", err)
 		}
-
 		stopped := false
 		s.mu.Lock()
 		stopped = s.noDebugProcess == nil // if it was stopped, this should be nil.
@@ -578,12 +639,12 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		s.mu.Unlock()
 
 		if !stopped {
+			s.logToConsole(proc.ErrProcessExited{Pid: cmd.ProcessState.Pid(), Status: cmd.ProcessState.ExitCode()}.Error())
 			s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")})
 		}
 		return
 	}
 
-	var err error
 	if s.debugger, err = debugger.New(&s.config.Debugger, s.config.ProcessArgs); err != nil {
 		s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 		return
@@ -613,8 +674,16 @@ func (s *Server) stopNoDebugProcess() {
 	s.noDebugProcess = nil
 	defer s.mu.Unlock()
 
-	// TODO(hyangah): gracefully terminate the process and its children processes.
-	if p != nil && !p.ProcessState.Exited() {
+	if p == nil {
+		// We already handled termination or there was never a process
+		return
+	}
+
+	if p.ProcessState.Exited() {
+		s.logToConsole(proc.ErrProcessExited{Pid: p.ProcessState.Pid(), Status: p.ProcessState.ExitCode()}.Error())
+	} else {
+		// TODO(hyangah): gracefully terminate the process and its children processes.
+		s.logToConsole(fmt.Sprintf("Terminating process %d", p.Process.Pid))
 		p.Process.Kill() // Don't check error. Process killing and self-termination may race.
 	}
 }
@@ -633,30 +702,42 @@ func isValidLaunchMode(launchMode interface{}) bool {
 // it disconnects the debuggee and signals that the debug adaptor
 // (in our case this TCP server) can be terminated.
 func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
-	s.send(&dap.DisconnectResponse{Response: *newResponse(request.Request)})
+	var err error
+	var exited error
 	if s.debugger != nil {
-		_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
+		state, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
 		if err != nil {
 			switch err.(type) {
-			case *proc.ErrProcessExited:
-				s.log.Debug(err)
+			case proc.ErrProcessExited:
+				exited = err
 			default:
 				s.log.Error(err)
 			}
+		} else if state.Exited {
+			exited = proc.ErrProcessExited{Pid: s.debugger.ProcessPid(), Status: state.ExitStatus}
 		}
 		// We always kill launched programs
 		kill := s.config.Debugger.AttachPid == 0
 		// In case of attach, we leave the program
-		// running but default, which can be
+		// running by default, which can be
 		// overridden by an explicit request to terminate.
 		if request.Arguments.TerminateDebuggee {
 			kill = true
 		}
+		if exited != nil {
+			s.logToConsole(exited.Error())
+			s.logToConsole("Detaching")
+		} else if kill {
+			s.logToConsole("Detaching and terminating target process")
+		} else {
+			s.logToConsole("Detaching without terminating target processs")
+		}
 		err = s.debugger.Detach(kill)
 		if err != nil {
 			switch err.(type) {
-			case *proc.ErrProcessExited:
-				s.log.Debug(err)
+			case proc.ErrProcessExited:
+				exited = err
+				s.logToConsole(exited.Error())
 			default:
 				s.log.Error(err)
 			}
@@ -664,6 +745,11 @@ func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
 	} else {
 		s.stopNoDebugProcess()
 	}
+	if err != nil && exited == nil {
+		s.sendErrorResponse(request.Request, DisconnectError, "Error while disconnecting", err.Error())
+	} else {
+		s.send(&dap.DisconnectResponse{Response: *newResponse(request.Request)})
+	}
 
 	// TODO(polina): make thread-safe when handlers become asynchronous.
 	s.signalDisconnect()
@@ -681,6 +767,9 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 		return
 	}
 
+	clientPath := request.Arguments.Source.Path
+	serverPath := s.toServerPath(clientPath)
+
 	// According to the spec we should "set multiple breakpoints for a single source
 	// and clear all previous breakpoints in that source." The simplest way is
 	// to clear all and then set all.
@@ -701,7 +790,7 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 		}
 		// Skip other source files.
 		// TODO(polina): should this be normalized because of different OSes?
-		if bp.File != request.Arguments.Source.Path {
+		if bp.File != serverPath {
 			continue
 		}
 		_, err := s.debugger.ClearBreakpoint(bp)
@@ -716,7 +805,7 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 	response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
 	for i, want := range request.Arguments.Breakpoints {
 		got, err := s.debugger.CreateBreakpoint(
-			&api.Breakpoint{File: request.Arguments.Source.Path, Line: want.Line, Cond: want.Condition})
+			&api.Breakpoint{File: serverPath, Line: want.Line, Cond: want.Condition})
 		response.Body.Breakpoints[i].Verified = (err == nil)
 		if err != nil {
 			response.Body.Breakpoints[i].Line = want.Line
@@ -724,7 +813,7 @@ func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
 		} else {
 			response.Body.Breakpoints[i].Id = got.ID
 			response.Body.Breakpoints[i].Line = got.Line
-			response.Body.Breakpoints[i].Source = dap.Source{Name: request.Arguments.Source.Name, Path: request.Arguments.Source.Path}
+			response.Body.Breakpoints[i].Source = dap.Source{Name: request.Arguments.Source.Name, Path: clientPath}
 		}
 	}
 	s.send(response)
@@ -772,7 +861,7 @@ func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) {
 	gs, _, err := s.debugger.Goroutines(0, 0)
 	if err != nil {
 		switch err.(type) {
-		case *proc.ErrProcessExited:
+		case proc.ErrProcessExited:
 			// If the program exits very quickly, the initial threads request will complete after it has exited.
 			// A TerminatedEvent has already been sent. Ignore the err returned in this case.
 			s.send(&dap.ThreadsResponse{Response: *newResponse(request.Request)})
@@ -838,8 +927,13 @@ func (s *Server) onAttachRequest(request *dap.AttachRequest) {
 			return
 		}
 		s.config.Debugger.AttachPid = int(pid)
-		s.setLaunchAttachArgs(request)
-		var err error
+		err := s.setLaunchAttachArgs(request)
+		if err != nil {
+			s.sendErrorResponse(request.Request,
+				FailedToAttach, "Failed to attach",
+				err.Error())
+			return
+		}
 		if s.debugger, err = debugger.New(&s.config.Debugger, nil); err != nil {
 			s.sendErrorResponse(request.Request,
 				FailedToAttach, "Failed to attach", err.Error())
@@ -880,6 +974,15 @@ func (s *Server) onStepOutRequest(request *dap.StepOutRequest) {
 	s.doStepCommand(api.StepOut, request.Arguments.ThreadId)
 }
 
+func stoppedGoroutineID(state *api.DebuggerState) (id int) {
+	if state.SelectedGoroutine != nil {
+		id = state.SelectedGoroutine.ID
+	} else if state.CurrentThread != nil {
+		id = state.CurrentThread.GoroutineID
+	}
+	return id
+}
+
 func (s *Server) doStepCommand(command string, threadId int) {
 	// Use SwitchGoroutine to change the current goroutine.
 	state, err := s.debugger.Command(&api.DebuggerCommand{Name: api.SwitchGoroutine, GoroutineID: threadId}, nil)
@@ -889,10 +992,8 @@ func (s *Server) doStepCommand(command string, threadId int) {
 		// since we already sent the step response.
 		stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
 		stopped.Body.AllThreadsStopped = true
-		if state.SelectedGoroutine != nil {
-			stopped.Body.ThreadId = state.SelectedGoroutine.ID
-		} else if state.CurrentThread != nil {
-			stopped.Body.ThreadId = state.CurrentThread.GoroutineID
+		if state != nil {
+			stopped.Body.ThreadId = stoppedGoroutineID(state)
 		}
 		stopped.Body.Reason = "error"
 		stopped.Body.Text = err.Error()
@@ -931,7 +1032,8 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) {
 		uniqueStackFrameID := s.stackFrameHandles.create(stackFrame{goroutineID, i})
 		stackFrames[i] = dap.StackFrame{Id: uniqueStackFrameID, Line: loc.Line, Name: fnName(loc)}
 		if loc.File != "<autogenerated>" {
-			stackFrames[i].Source = dap.Source{Name: filepath.Base(loc.File), Path: loc.File}
+			clientPath := s.toClientPath(loc.File)
+			stackFrames[i].Source = dap.Source{Name: filepath.Base(clientPath), Path: clientPath}
 		}
 		stackFrames[i].Column = 0
 	}
@@ -1272,7 +1374,7 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 // variables, so consider also adding the following:
 // -- print {expression} - return the result as a string like from dlv cli
 func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
-	showErrorToUser := request.Arguments.Context != "watch"
+	showErrorToUser := request.Arguments.Context != "watch" && request.Arguments.Context != "repl"
 	if s.debugger == nil {
 		s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", "debugger is nil", showErrorToUser)
 		return
@@ -1342,9 +1444,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			s.resetHandlesForStop()
 			stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
 			stopped.Body.AllThreadsStopped = true
-			if state.SelectedGoroutine != nil {
-				stopped.Body.ThreadId = state.SelectedGoroutine.ID
-			}
+			stopped.Body.ThreadId = stoppedGoroutineID(state)
 			stopped.Body.Reason = s.debugger.StopReason().String()
 			s.send(stopped)
 			// TODO(polina): once this is asynchronous, we could wait to reply until the user
@@ -1543,9 +1643,7 @@ func (s *Server) doCommand(command string) {
 	stopped.Body.AllThreadsStopped = true
 
 	if err == nil {
-		if state.SelectedGoroutine != nil {
-			stopped.Body.ThreadId = state.SelectedGoroutine.ID
-		}
+		stopped.Body.ThreadId = stoppedGoroutineID(state)
 
 		switch s.debugger.StopReason() {
 		case proc.StopNextFinished:
@@ -1553,7 +1651,7 @@ func (s *Server) doCommand(command string) {
 		default:
 			stopped.Body.Reason = "breakpoint"
 		}
-		if state.CurrentThread.Breakpoint != nil {
+		if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil {
 			switch state.CurrentThread.Breakpoint.Name {
 			case proc.FatalThrow:
 				stopped.Body.Reason = "fatal error"
@@ -1572,7 +1670,7 @@ func (s *Server) doCommand(command string) {
 		}
 		state, err := s.debugger.State( /*nowait*/ true)
 		if err == nil {
-			stopped.Body.ThreadId = state.CurrentThread.GoroutineID
+			stopped.Body.ThreadId = stoppedGoroutineID(state)
 		}
 		s.send(stopped)
 
@@ -1590,3 +1688,25 @@ func (s *Server) doCommand(command string) {
 			}})
 	}
 }
+
+func (s *Server) toClientPath(path string) string {
+	if len(s.args.substitutePathServerToClient) == 0 {
+		return path
+	}
+	clientPath := locspec.SubstitutePath(path, s.args.substitutePathServerToClient)
+	if clientPath != path {
+		s.log.Debugf("server path=%s converted to client path=%s\n", path, clientPath)
+	}
+	return clientPath
+}
+
+func (s *Server) toServerPath(path string) string {
+	if len(s.args.substitutePathClientToServer) == 0 {
+		return path
+	}
+	serverPath := locspec.SubstitutePath(path, s.args.substitutePathClientToServer)
+	if serverPath != path {
+		s.log.Debugf("client path=%s converted to server path=%s\n", path, serverPath)
+	}
+	return serverPath
+}
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index af306043..6504bc02 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -376,8 +376,6 @@ func (d *Debugger) LastModified() time.Time {
 	return d.target.BinInfo().LastModified()
 }
 
-const deferReturn = "runtime.deferreturn"
-
 // FunctionReturnLocations returns all return locations
 // for the given function, a list of addresses corresponding
 // to 'ret' or 'call runtime.deferreturn'.
@@ -511,23 +509,27 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
 	discarded := []api.DiscardedBreakpoint{}
 	breakpoints := api.ConvertBreakpoints(d.breakpoints())
 	d.target = p
+	maxID := 0
 	for _, oldBp := range breakpoints {
 		if oldBp.ID < 0 {
 			continue
 		}
+		if oldBp.ID > maxID {
+			maxID = oldBp.ID
+		}
 		if len(oldBp.File) > 0 {
 			addrs, err := proc.FindFileLocation(p, oldBp.File, oldBp.Line)
 			if err != nil {
 				discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
 				continue
 			}
-			createLogicalBreakpoint(d, addrs, oldBp)
+			createLogicalBreakpoint(d, addrs, oldBp, oldBp.ID)
 		} else {
 			// Avoid setting a breakpoint based on address when rebuilding
 			if rebuild {
 				continue
 			}
-			newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil)
+			newBp, err := p.SetBreakpointWithID(oldBp.ID, oldBp.Addr)
 			if err != nil {
 				return nil, err
 			}
@@ -536,6 +538,12 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
 			}
 		}
 	}
+	for _, bp := range d.disabledBreakpoints {
+		if bp.ID > maxID {
+			maxID = bp.ID
+		}
+	}
+	d.target.SetNextBreakpointID(maxID)
 	return discarded, nil
 }
 
@@ -576,7 +584,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
 
 	exited := false
 	if _, err := d.target.Valid(); err != nil {
-		_, exited = err.(*proc.ErrProcessExited)
+		_, exited = err.(proc.ErrProcessExited)
 	}
 
 	state = &api.DebuggerState{
@@ -654,7 +662,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
 		return nil, err
 	}
 
-	createdBp, err := createLogicalBreakpoint(d, addrs, requestedBp)
+	createdBp, err := createLogicalBreakpoint(d, addrs, requestedBp, 0)
 	if err != nil {
 		return nil, err
 	}
@@ -664,7 +672,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
 
 // createLogicalBreakpoint creates one physical breakpoint for each address
 // in addrs and associates all of them with the same logical breakpoint.
-func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
+func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
 	p := d.target
 
 	if dbp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
@@ -674,7 +682,11 @@ func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Break
 	bps := make([]*proc.Breakpoint, len(addrs))
 	var err error
 	for i := range addrs {
-		bps[i], err = p.SetBreakpoint(addrs[i], proc.UserBreakpoint, nil)
+		if id > 0 {
+			bps[i], err = p.SetBreakpointWithID(id, addrs[i])
+		} else {
+			bps[i], err = p.SetBreakpoint(addrs[i], proc.UserBreakpoint, nil)
+		}
 		if err != nil {
 			break
 		}
@@ -873,7 +885,7 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
 func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
 	var bps []*proc.Breakpoint
 	for _, bp := range d.target.Breakpoints().M {
-		if bp.LogicalID == id {
+		if bp.IsUser() && bp.LogicalID == id {
 			bps = append(bps, bp)
 		}
 	}


Test patch:
diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md
index c79bd2a8..e49f5b20 100644
--- a/Documentation/backend_test_health.md
+++ b/Documentation/backend_test_health.md
@@ -1,23 +1,23 @@
 Tests skipped by each supported backend:
 
-* 386 skipped = 2.7% (4/149)
+* 386 skipped = 2.7% (4/147)
 	* 1 broken
 	* 3 broken - cgo stacktraces
-* arm64 skipped = 2.7% (4/149)
-	* 2 broken
+* arm64 skipped = 2% (3/147)
+	* 1 broken
 	* 1 broken - cgo stacktraces
 	* 1 broken - global variable symbolication
-* darwin/arm64 skipped = 0.67% (1/149)
+* darwin/arm64 skipped = 0.68% (1/147)
 	* 1 broken - cgo stacktraces
-* darwin/lldb skipped = 0.67% (1/149)
+* darwin/lldb skipped = 0.68% (1/147)
 	* 1 upstream issue
-* freebsd skipped = 8.1% (12/149)
+* freebsd skipped = 8.2% (12/147)
 	* 11 broken
 	* 1 not implemented
-* linux/386/pie skipped = 0.67% (1/149)
+* linux/386/pie skipped = 0.68% (1/147)
 	* 1 broken
-* pie skipped = 0.67% (1/149)
+* pie skipped = 0.68% (1/147)
 	* 1 upstream issue - https://github.com/golang/go/issues/29322
-* windows skipped = 1.3% (2/149)
+* windows skipped = 1.4% (2/147)
 	* 1 broken
 	* 1 upstream issue
diff --git a/_scripts/test_linux.sh b/_scripts/test_linux.sh
index 387a1345..2125af5f 100755
--- a/_scripts/test_linux.sh
+++ b/_scripts/test_linux.sh
@@ -3,7 +3,7 @@ set -e
 set -x
 
 apt-get -qq update
-apt-get install -y dwz wget make git gcc curl jq
+apt-get install -y dwz wget make git gcc curl jq lsof
 dwz --version
 
 version=$1
diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go
index cea41693..24e62762 100644
--- a/pkg/dwarf/line/state_machine_test.go
+++ b/pkg/dwarf/line/state_machine_test.go
@@ -30,11 +30,6 @@ func slurpGzip(path string) ([]byte, error) {
 	return ioutil.ReadAll(gzin)
 }
 
-const (
-	newCompileUnit = "NEW COMPILE UNIT"
-	debugLineEnd   = "END"
-)
-
 func TestGrafana(t *testing.T) {
 	// Compares a full execution of our state machine on the debug_line section
 	// of grafana to the output generated using debug/dwarf.LineReader on the
diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go
index c7935dac..573707f8 100644
--- a/pkg/proc/dwarf_expr_test.go
+++ b/pkg/proc/dwarf_expr_test.go
@@ -321,9 +321,6 @@ func TestIssue1419(t *testing.T) {
 }
 
 func TestLocationCovers(t *testing.T) {
-	const before = 0x1234
-	const after = 0x4321
-
 	dwb := dwarfbuilder.New()
 
 	uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2)
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index c25f7799..9136073a 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -1310,23 +1310,9 @@ func TestFrameEvaluation(t *testing.T) {
 			found[vval] = true
 		}
 
-		firsterr := false
-		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
-			// We try to make sure that all goroutines are stopped at a sensible place
-			// before reading their stacktrace, but due to the nature of the test
-			// program there is no guarantee that we always find them in a reasonable
-			// state.
-			// Asynchronous preemption in Go 1.14 exacerbates this problem, to avoid
-			// unnecessary flakiness allow a single goroutine to be in a bad state.
-			firsterr = true
-		}
 		for i := range found {
 			if !found[i] {
-				if firsterr {
-					firsterr = false
-				} else {
-					t.Fatalf("Goroutine %d not found\n", i)
-				}
+				t.Fatalf("Goroutine %d not found\n", i)
 			}
 		}
 
@@ -1734,24 +1720,6 @@ func TestStepIntoFunction(t *testing.T) {
 	})
 }
 
-func TestIssue384(t *testing.T) {
-	// Crash related to reading uninitialized memory, introduced by the memory prefetching optimization
-
-	ver, _ := goversion.Parse(runtime.Version())
-	if ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
-		// go 1.10 emits DW_AT_decl_line and we won't be able to evaluate 'st'
-		// which is declared after line 13.
-		t.Skip("can not evaluate not-yet-declared variables with go 1.10")
-	}
-
-	protest.AllowRecording(t)
-	withTestProcess("issue384", t, func(p *proc.Target, fixture protest.Fixture) {
-		setFileBreakpoint(p, t, fixture.Source, 13)
-		assertNoError(p.Continue(), t, "Continue()")
-		evalVariable(p, t, "st")
-	})
-}
-
 func TestIssue332_Part1(t *testing.T) {
 	// Next shouldn't step inside a function call
 	protest.AllowRecording(t)
@@ -1817,17 +1785,6 @@ func TestIssue332_Part2(t *testing.T) {
 	})
 }
 
-func TestIssue396(t *testing.T) {
-	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
-		// CL 161337 in Go 1.13 and later removes the autogenerated init function
-		// https://go-review.googlesource.com/c/go/+/161337
-		t.Skip("no autogenerated init function in Go 1.13 or later")
-	}
-	withTestProcess("callme", t, func(p *proc.Target, fixture protest.Fixture) {
-		findFunctionLocation(p, t, "main.init")
-	})
-}
-
 func TestIssue414(t *testing.T) {
 	skipOn(t, "broken", "linux", "386", "pie") // test occasionally hangs on linux/386/pie
 	// Stepping until the program exits
@@ -2507,8 +2464,6 @@ func TestStepOutDeferReturnAndDirectCall(t *testing.T) {
 		{contStepout, 28}})
 }
 
-var maxInstructionLength uint64
-
 func TestStepOnCallPtrInstr(t *testing.T) {
 	protest.AllowRecording(t)
 	withTestProcess("teststepprog", t, func(p *proc.Target, fixture protest.Fixture) {
@@ -2824,9 +2779,6 @@ func TestAttachDetach(t *testing.T) {
 	if testBackend == "rr" {
 		return
 	}
-	if os.Getenv("CI") == "true" {
-		t.Skip("disabled on Github actions")
-	}
 	var buildFlags protest.BuildFlags
 	if buildMode == "pie" {
 		buildFlags |= protest.BuildModePIE
@@ -3960,7 +3912,6 @@ func TestIssue951(t *testing.T) {
 }
 
 func TestDWZCompression(t *testing.T) {
-	skipOn(t, "broken", "arm64")
 	// If dwz is not available in the system, skip this test
 	if _, err := exec.LookPath("dwz"); err != nil {
 		t.Skip("dwz not installed")
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index 14069a5a..7d6bd878 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -254,9 +254,6 @@ func TestIssue354(t *testing.T) {
 }
 
 func TestIssue411(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	test.AllowRecording(t)
 	withTestTerminal("math", t, func(term *FakeTerminal) {
 		term.MustExec("break _fixtures/math.go:8")
@@ -270,9 +267,6 @@ func TestIssue411(t *testing.T) {
 }
 
 func TestTrace(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	test.AllowRecording(t)
 	withTestTerminal("issue573", t, func(term *FakeTerminal) {
 		term.MustExec("trace foo")
@@ -289,9 +283,6 @@ func TestTrace(t *testing.T) {
 }
 
 func TestTraceWithName(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	test.AllowRecording(t)
 	withTestTerminal("issue573", t, func(term *FakeTerminal) {
 		term.MustExec("trace foobar foo")
@@ -308,9 +299,6 @@ func TestTraceWithName(t *testing.T) {
 }
 
 func TestTraceOnNonFunctionEntry(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	test.AllowRecording(t)
 	withTestTerminal("issue573", t, func(term *FakeTerminal) {
 		term.MustExec("trace foobar issue573.go:19")
@@ -480,9 +468,6 @@ func TestScopePrefix(t *testing.T) {
 }
 
 func TestOnPrefix(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	if runtime.GOOS == "freebsd" {
 		t.Skip("test is not valid on FreeBSD")
 	}
@@ -539,9 +524,6 @@ func TestNoVars(t *testing.T) {
 }
 
 func TestOnPrefixLocals(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	if runtime.GOOS == "freebsd" {
 		t.Skip("test is not valid on FreeBSD")
 	}
@@ -600,9 +582,6 @@ func countOccurrences(s, needle string) int {
 }
 
 func TestIssue387(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	if runtime.GOOS == "freebsd" {
 		t.Skip("test is not valid on FreeBSD")
 	}
@@ -875,25 +854,6 @@ func TestConfig(t *testing.T) {
 	}
 }
 
-func TestDisassembleAutogenerated(t *testing.T) {
-	// Executing the 'disassemble' command on autogenerated code should work correctly
-
-	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
-		// CL 161337 in Go 1.13 and later removes the autogenerated init function
-		// https://go-review.googlesource.com/c/go/+/161337
-		t.Skip("no autogenerated init function in Go 1.13 or later")
-	}
-
-	withTestTerminal("math", t, func(term *FakeTerminal) {
-		term.MustExec("break main.init")
-		term.MustExec("continue")
-		out := term.MustExec("disassemble")
-		if !strings.Contains(out, "TEXT main.init(SB) ") {
-			t.Fatalf("output of disassemble wasn't for the main.init function %q", out)
-		}
-	})
-}
-
 func TestIssue1090(t *testing.T) {
 	// Exit while executing 'next' should report the "Process exited" error
 	// message instead of crashing.
@@ -1000,9 +960,6 @@ func TestTruncateStacktrace(t *testing.T) {
 }
 
 func TestIssue1493(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FpRegs for now")
-	}
 	// The 'regs' command without the '-a' option should only return
 	// general purpose registers.
 	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
diff --git a/pkg/terminal/starlark_test.go b/pkg/terminal/starlark_test.go
index b96b5f15..1cdee0cb 100644
--- a/pkg/terminal/starlark_test.go
+++ b/pkg/terminal/starlark_test.go
@@ -2,15 +2,11 @@ package terminal
 
 import (
 	"fmt"
-	"runtime"
 	"strings"
 	"testing"
 )
 
 func TestStarlarkExamples(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("test is not valid on ARM64")
-	}
 	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
 		term.MustExec("continue")
 		t.Run("goroutine_start_line", func(t *testing.T) { testStarlarkExampleGoroutineStartLine(t, term) })
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index 70c1d22e..e2390c8f 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -8,6 +8,7 @@ import (
 	"log"
 	"net"
 	"path/filepath"
+	"regexp"
 	"testing"
 
 	"github.com/google/go-dap"
@@ -50,18 +51,18 @@ func (c *Client) ReadMessage() (dap.Message, error) {
 	return dap.ReadProtocolMessage(c.reader)
 }
 
-func (c *Client) expectReadProtocolMessage(t *testing.T) dap.Message {
+func (c *Client) ExpectMessage(t *testing.T) dap.Message {
 	t.Helper()
 	m, err := dap.ReadProtocolMessage(c.reader)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 	return m
 }
 
 func (c *Client) ExpectErrorResponse(t *testing.T) *dap.ErrorResponse {
 	t.Helper()
-	er := c.expectReadProtocolMessage(t).(*dap.ErrorResponse)
+	er := c.ExpectMessage(t).(*dap.ErrorResponse)
 	if er.Body.Error.ShowUser {
 		t.Errorf("\ngot %#v\nwant ShowUser=false", er)
 	}
@@ -70,7 +71,7 @@ func (c *Client) ExpectErrorResponse(t *testing.T) *dap.ErrorResponse {
 
 func (c *Client) ExpectVisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
 	t.Helper()
-	er := c.expectReadProtocolMessage(t).(*dap.ErrorResponse)
+	er := c.ExpectMessage(t).(*dap.ErrorResponse)
 	if !er.Body.Error.ShowUser {
 		t.Errorf("\ngot %#v\nwant ShowUser=true", er)
 	}
@@ -79,7 +80,7 @@ func (c *Client) ExpectVisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
 
 func (c *Client) expectErrorResponse(t *testing.T, id int, message string) *dap.ErrorResponse {
 	t.Helper()
-	er := c.expectReadProtocolMessage(t).(*dap.ErrorResponse)
+	er := c.ExpectMessage(t).(*dap.ErrorResponse)
 	if er.Body.Error.Id != id || er.Message != message {
 		t.Errorf("\ngot %#v\nwant Id=%d Message=%q", er, id, message)
 	}
@@ -98,37 +99,37 @@ func (c *Client) ExpectUnsupportedCommandErrorResponse(t *testing.T) *dap.ErrorR
 
 func (c *Client) ExpectDisconnectResponse(t *testing.T) *dap.DisconnectResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.DisconnectResponse)
+	return c.ExpectMessage(t).(*dap.DisconnectResponse)
 }
 
 func (c *Client) ExpectContinueResponse(t *testing.T) *dap.ContinueResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ContinueResponse)
+	return c.ExpectMessage(t).(*dap.ContinueResponse)
 }
 
 func (c *Client) ExpectNextResponse(t *testing.T) *dap.NextResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.NextResponse)
+	return c.ExpectMessage(t).(*dap.NextResponse)
 }
 
 func (c *Client) ExpectStepInResponse(t *testing.T) *dap.StepInResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.StepInResponse)
+	return c.ExpectMessage(t).(*dap.StepInResponse)
 }
 
 func (c *Client) ExpectStepOutResponse(t *testing.T) *dap.StepOutResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.StepOutResponse)
+	return c.ExpectMessage(t).(*dap.StepOutResponse)
 }
 
 func (c *Client) ExpectTerminatedEvent(t *testing.T) *dap.TerminatedEvent {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.TerminatedEvent)
+	return c.ExpectMessage(t).(*dap.TerminatedEvent)
 }
 
 func (c *Client) ExpectInitializeResponse(t *testing.T) *dap.InitializeResponse {
 	t.Helper()
-	initResp := c.expectReadProtocolMessage(t).(*dap.InitializeResponse)
+	initResp := c.ExpectMessage(t).(*dap.InitializeResponse)
 	if !initResp.Body.SupportsConfigurationDoneRequest {
 		t.Errorf("got %#v, want SupportsConfigurationDoneRequest=true", initResp)
 	}
@@ -137,167 +138,196 @@ func (c *Client) ExpectInitializeResponse(t *testing.T) *dap.InitializeResponse
 
 func (c *Client) ExpectInitializedEvent(t *testing.T) *dap.InitializedEvent {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.InitializedEvent)
+	return c.ExpectMessage(t).(*dap.InitializedEvent)
 }
 
 func (c *Client) ExpectLaunchResponse(t *testing.T) *dap.LaunchResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.LaunchResponse)
+	return c.ExpectMessage(t).(*dap.LaunchResponse)
 }
 
 func (c *Client) ExpectAttachResponse(t *testing.T) *dap.AttachResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.AttachResponse)
+	return c.ExpectMessage(t).(*dap.AttachResponse)
 }
 
 func (c *Client) ExpectSetExceptionBreakpointsResponse(t *testing.T) *dap.SetExceptionBreakpointsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.SetExceptionBreakpointsResponse)
+	return c.ExpectMessage(t).(*dap.SetExceptionBreakpointsResponse)
 }
 
 func (c *Client) ExpectSetBreakpointsResponse(t *testing.T) *dap.SetBreakpointsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.SetBreakpointsResponse)
+	return c.ExpectMessage(t).(*dap.SetBreakpointsResponse)
 }
 
 func (c *Client) ExpectStoppedEvent(t *testing.T) *dap.StoppedEvent {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.StoppedEvent)
+	return c.ExpectMessage(t).(*dap.StoppedEvent)
 }
 
 func (c *Client) ExpectOutputEvent(t *testing.T) *dap.OutputEvent {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.OutputEvent)
+	return c.ExpectMessage(t).(*dap.OutputEvent)
+}
+
+func (c *Client) ExpectOutputEventRegex(t *testing.T, want string) *dap.OutputEvent {
+	t.Helper()
+	e := c.ExpectMessage(t).(*dap.OutputEvent)
+	if matched, _ := regexp.MatchString(want, e.Body.Output); !matched {
+		t.Errorf("\ngot %#v\nwant Output=%q", e, want)
+	}
+	return e
+}
+
+func (c *Client) ExpectOutputEventProcessExited(t *testing.T, status int) *dap.OutputEvent {
+	t.Helper()
+	return c.ExpectOutputEventRegex(t, fmt.Sprintf(`Process [0-9]+ has exited with status %d\n`, status))
+}
+
+func (c *Client) ExpectOutputEventDetaching(t *testing.T) *dap.OutputEvent {
+	t.Helper()
+	return c.ExpectOutputEventRegex(t, `Detaching\n`)
+}
+
+func (c *Client) ExpectOutputEventDetachingKill(t *testing.T) *dap.OutputEvent {
+	t.Helper()
+	return c.ExpectOutputEventRegex(t, `Detaching and terminating target process\n`)
+}
+
+func (c *Client) ExpectOutputEventDetachingNoKill(t *testing.T) *dap.OutputEvent {
+	t.Helper()
+	return c.ExpectOutputEventRegex(t, `Detaching without terminating target process\n`)
 }
 
 func (c *Client) ExpectConfigurationDoneResponse(t *testing.T) *dap.ConfigurationDoneResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ConfigurationDoneResponse)
+	return c.ExpectMessage(t).(*dap.ConfigurationDoneResponse)
 }
 
 func (c *Client) ExpectThreadsResponse(t *testing.T) *dap.ThreadsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ThreadsResponse)
+	return c.ExpectMessage(t).(*dap.ThreadsResponse)
 }
 
 func (c *Client) ExpectStackTraceResponse(t *testing.T) *dap.StackTraceResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.StackTraceResponse)
+	return c.ExpectMessage(t).(*dap.StackTraceResponse)
 }
 
 func (c *Client) ExpectScopesResponse(t *testing.T) *dap.ScopesResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ScopesResponse)
+	return c.ExpectMessage(t).(*dap.ScopesResponse)
 }
 
 func (c *Client) ExpectVariablesResponse(t *testing.T) *dap.VariablesResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.VariablesResponse)
+	return c.ExpectMessage(t).(*dap.VariablesResponse)
 }
 
 func (c *Client) ExpectEvaluateResponse(t *testing.T) *dap.EvaluateResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.EvaluateResponse)
+	return c.ExpectMessage(t).(*dap.EvaluateResponse)
 }
 
 func (c *Client) ExpectTerminateResponse(t *testing.T) *dap.TerminateResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.TerminateResponse)
+	return c.ExpectMessage(t).(*dap.TerminateResponse)
 }
 
 func (c *Client) ExpectRestartResponse(t *testing.T) *dap.RestartResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.RestartResponse)
+	return c.ExpectMessage(t).(*dap.RestartResponse)
 }
 
 func (c *Client) ExpectSetFunctionBreakpointsResponse(t *testing.T) *dap.SetFunctionBreakpointsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.SetFunctionBreakpointsResponse)
+	return c.ExpectMessage(t).(*dap.SetFunctionBreakpointsResponse)
 }
 
 func (c *Client) ExpectStepBackResponse(t *testing.T) *dap.StepBackResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.StepBackResponse)
+	return c.ExpectMessage(t).(*dap.StepBackResponse)
 }
 
 func (c *Client) ExpectReverseContinueResponse(t *testing.T) *dap.ReverseContinueResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ReverseContinueResponse)
+	return c.ExpectMessage(t).(*dap.ReverseContinueResponse)
 }
 
 func (c *Client) ExpectRestartFrameResponse(t *testing.T) *dap.RestartFrameResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.RestartFrameResponse)
+	return c.ExpectMessage(t).(*dap.RestartFrameResponse)
 }
 
 func (c *Client) ExpectSetExpressionResponse(t *testing.T) *dap.SetExpressionResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.SetExpressionResponse)
+	return c.ExpectMessage(t).(*dap.SetExpressionResponse)
 }
 
 func (c *Client) ExpectTerminateThreadsResponse(t *testing.T) *dap.TerminateThreadsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.TerminateThreadsResponse)
+	return c.ExpectMessage(t).(*dap.TerminateThreadsResponse)
 }
 
 func (c *Client) ExpectStepInTargetsResponse(t *testing.T) *dap.StepInTargetsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.StepInTargetsResponse)
+	return c.ExpectMessage(t).(*dap.StepInTargetsResponse)
 }
 
 func (c *Client) ExpectGotoTargetsResponse(t *testing.T) *dap.GotoTargetsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.GotoTargetsResponse)
+	return c.ExpectMessage(t).(*dap.GotoTargetsResponse)
 }
 
 func (c *Client) ExpectCompletionsResponse(t *testing.T) *dap.CompletionsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.CompletionsResponse)
+	return c.ExpectMessage(t).(*dap.CompletionsResponse)
 }
 
 func (c *Client) ExpectExceptionInfoResponse(t *testing.T) *dap.ExceptionInfoResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ExceptionInfoResponse)
+	return c.ExpectMessage(t).(*dap.ExceptionInfoResponse)
 }
 
 func (c *Client) ExpectLoadedSourcesResponse(t *testing.T) *dap.LoadedSourcesResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.LoadedSourcesResponse)
+	return c.ExpectMessage(t).(*dap.LoadedSourcesResponse)
 }
 
 func (c *Client) ExpectDataBreakpointInfoResponse(t *testing.T) *dap.DataBreakpointInfoResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.DataBreakpointInfoResponse)
+	return c.ExpectMessage(t).(*dap.DataBreakpointInfoResponse)
 }
 
 func (c *Client) ExpectSetDataBreakpointsResponse(t *testing.T) *dap.SetDataBreakpointsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.SetDataBreakpointsResponse)
+	return c.ExpectMessage(t).(*dap.SetDataBreakpointsResponse)
 }
 
 func (c *Client) ExpectReadMemoryResponse(t *testing.T) *dap.ReadMemoryResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ReadMemoryResponse)
+	return c.ExpectMessage(t).(*dap.ReadMemoryResponse)
 }
 
 func (c *Client) ExpectDisassembleResponse(t *testing.T) *dap.DisassembleResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.DisassembleResponse)
+	return c.ExpectMessage(t).(*dap.DisassembleResponse)
 }
 
 func (c *Client) ExpectCancelResponse(t *testing.T) *dap.CancelResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.CancelResponse)
+	return c.ExpectMessage(t).(*dap.CancelResponse)
 }
 
 func (c *Client) ExpectBreakpointLocationsResponse(t *testing.T) *dap.BreakpointLocationsResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.BreakpointLocationsResponse)
+	return c.ExpectMessage(t).(*dap.BreakpointLocationsResponse)
 }
 
 func (c *Client) ExpectModulesResponse(t *testing.T) *dap.ModulesResponse {
 	t.Helper()
-	return c.expectReadProtocolMessage(t).(*dap.ModulesResponse)
+	return c.ExpectMessage(t).(*dap.ModulesResponse)
 }
 
 // InitializeRequest sends an 'initialize' request.
@@ -317,7 +347,7 @@ func (c *Client) InitializeRequest() {
 }
 
 // LaunchRequest sends a 'launch' request with the specified args.
-func (c *Client) LaunchRequest(mode string, program string, stopOnEntry bool) {
+func (c *Client) LaunchRequest(mode, program string, stopOnEntry bool) {
 	request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
 	request.Arguments = map[string]interface{}{
 		"request":     "launch",
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index b6a66440..3faad362 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -113,6 +113,8 @@ func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.F
 //                                 : 12 << continue
 // - Program runs to completion    :    << terminated event
 //                                 : 13 >> disconnect
+//                                 :    << output event (Process exited)
+//                                 :    << output event (Detaching)
 //                                 : 13 << disconnect
 // This test exhaustively tests Seq and RequestSeq on all messages from the
 // server. Other tests do not necessarily need to repeat all these checks.
@@ -197,7 +199,7 @@ func TestLaunchStopOnEntry(t *testing.T) {
 
 		// 10 >> evaluate, << error
 		client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
-		erResp := client.ExpectVisibleErrorResponse(t)
+		erResp := client.ExpectErrorResponse(t)
 		if erResp.Seq != 0 || erResp.RequestSeq != 10 || erResp.Body.Error.Id != 2009 {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=2009", erResp)
 		}
@@ -222,6 +224,14 @@ func TestLaunchStopOnEntry(t *testing.T) {
 
 		// 13 >> disconnect, << disconnect
 		client.DisconnectRequest()
+		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 || oep.Body.Category != "console" {
+			t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
+		}
 		dResp := client.ExpectDisconnectResponse(t)
 		if dResp.Seq != 0 || dResp.RequestSeq != 13 {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=13", dResp)
@@ -323,7 +333,7 @@ func TestAttachStopOnEntry(t *testing.T) {
 
 		// 10 >> evaluate, << error
 		client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
-		erResp := client.ExpectVisibleErrorResponse(t)
+		erResp := client.ExpectErrorResponse(t)
 		if erResp.Seq != 0 || erResp.RequestSeq != 10 || erResp.Body.Error.Id != 2009 {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=2009", erResp)
 		}
@@ -344,6 +354,10 @@ func TestAttachStopOnEntry(t *testing.T) {
 
 		// 12 >> 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)
+		}
 		dResp := client.ExpectDisconnectResponse(t)
 		if dResp.Seq != 0 || dResp.RequestSeq != 12 {
 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12", dResp)
@@ -398,6 +412,8 @@ func TestContinueOnEntry(t *testing.T) {
 
 		// 7 >> 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)
@@ -511,6 +527,8 @@ func TestPreSetBreakpoint(t *testing.T) {
 
 		client.ExpectTerminatedEvent(t)
 		client.DisconnectRequest()
+		client.ExpectOutputEventProcessExited(t, 0)
+		client.ExpectOutputEventDetaching(t)
 		client.ExpectDisconnectResponse(t)
 	})
 }
@@ -678,7 +696,6 @@ func validateEvaluateName(t *testing.T, client *daptest.Client, got *dap.Variabl
 func TestStackTraceRequest(t *testing.T) {
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 		var stResp *dap.StackTraceResponse
-		const StartHandle = 1000 // from handles.go
 		runDebugSessionWithBPs(t, client, "launch",
 			// Launch
 			func() {
@@ -1686,6 +1703,95 @@ func TestSetBreakpoint(t *testing.T) {
 	})
 }
 
+// TestLaunchSubstitutePath sets a breakpoint using a path
+// that does not exist and expects the substitutePath attribute
+// in the launch configuration to take care of the mapping.
+func TestLaunchSubstitutePath(t *testing.T) {
+	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
+		substitutePathTestHelper(t, fixture, client, "launch", map[string]interface{}{"mode": "exec", "program": fixture.Path})
+	})
+}
+
+// TestAttachSubstitutePath sets a breakpoint using a path
+// that does not exist and expects the substitutePath attribute
+// in the launch configuration to take care of the mapping.
+func TestAttachSubstitutePath(t *testing.T) {
+	if runtime.GOOS == "freebsd" {
+		t.SkipNow()
+	}
+	if runtime.GOOS == "windows" {
+		t.Skip("test skipped on windows, see https://delve.beta.teamcity.com/project/Delve_windows for details")
+	}
+	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
+		cmd := execFixture(t, fixture)
+
+		substitutePathTestHelper(t, fixture, client, "attach", map[string]interface{}{"mode": "local", "processId": cmd.Process.Pid})
+	})
+}
+
+func substitutePathTestHelper(t *testing.T, fixture protest.Fixture, client *daptest.Client, request string, launchAttachConfig map[string]interface{}) {
+	t.Helper()
+	nonexistentDir := filepath.Join(string(filepath.Separator), "path", "that", "does", "not", "exist")
+	if runtime.GOOS == "windows" {
+		nonexistentDir = "C:" + nonexistentDir
+	}
+
+	launchAttachConfig["stopOnEntry"] = false
+	// The rules in 'substitutePath' will be applied as follows:
+	// - mapping paths from client to server:
+	//		The first rule["from"] to match a prefix of 'path' will be applied:
+	//			strings.Replace(path, rule["from"], rule["to"], 1)
+	// - mapping paths from server to client:
+	//		The first rule["to"] to match a prefix of 'path' will be applied:
+	//			strings.Replace(path, rule["to"], rule["from"], 1)
+	launchAttachConfig["substitutePath"] = []map[string]string{
+		{"from": nonexistentDir, "to": filepath.Dir(fixture.Source)},
+		// Since the path mappings are ordered, when converting from client path to
+		// server path, this mapping will not apply, because nonexistentDir appears in
+		// an earlier rule.
+		{"from": nonexistentDir, "to": "this_is_a_bad_path"},
+		// Since the path mappings are ordered, when converting from server path to
+		// client path, this mapping will not apply, because filepath.Dir(fixture.Source)
+		// appears in an earlier rule.
+		{"from": "this_is_a_bad_path", "to": filepath.Dir(fixture.Source)},
+	}
+
+	runDebugSessionWithBPs(t, client, request,
+		func() {
+			switch request {
+			case "attach":
+				client.AttachRequest(launchAttachConfig)
+			case "launch":
+				client.LaunchRequestWithArgs(launchAttachConfig)
+			default:
+				t.Fatalf("invalid request: %s", request)
+			}
+		},
+		// Set breakpoints
+		filepath.Join(nonexistentDir, "loopprog.go"), []int{8},
+		[]onBreakpoint{{
+
+			execute: func() {
+				handleStop(t, client, 1, "main.loop", 8)
+			},
+			disconnect: true,
+		}})
+}
+
+// execFixture runs the binary fixture.Path and hooks up stdout and stderr
+// to os.Stdout and os.Stderr.
+func execFixture(t *testing.T, fixture protest.Fixture) *exec.Cmd {
+	t.Helper()
+	// TODO(polina): do I need to sanity check testBackend and runtime.GOOS?
+	cmd := exec.Command(fixture.Path)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Start(); err != nil {
+		t.Fatal(err)
+	}
+	return cmd
+}
+
 // TestWorkingDir executes to a breakpoint and tests that the specified
 // working directory is the one used to run the program.
 func TestWorkingDir(t *testing.T) {
@@ -2019,8 +2125,8 @@ func TestEvaluateCallRequest(t *testing.T) {
 					}
 					// Call can exit
 					client.EvaluateRequest("call callexit()", 1000, "this context will be ignored")
-					client.ExpectTerminatedEvent(t)
 				},
+				terminated: true,
 				disconnect: true,
 			}})
 	})
@@ -2236,12 +2342,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
 					client.StepOutRequest(goroutineId)
 					client.ExpectStepOutResponse(t)
 
-					m, err := client.ReadMessage()
-					if err != nil {
-						t.Fatal(err)
-					}
-
-					switch e := m.(type) {
+					switch e := client.ExpectMessage(t).(type) {
 					case *dap.StoppedEvent:
 						if e.Body.ThreadId != goroutineId {
 							t.Fatalf("StepOut did not continue on the selected goroutine, expected %d got %d", goroutineId, e.Body.ThreadId)
@@ -2433,9 +2534,12 @@ func handleStop(t *testing.T, client *daptest.Client, thread int, name string, l
 // disconnect is true, the test harness will abort the program
 // execution. Otherwise, a continue will be issued and the
 // program will continue to the next breakpoint or termination.
+// If terminated is true, we expect requests at this breakpoint
+// to result in termination.
 type onBreakpoint struct {
 	execute    func()
 	disconnect bool
+	terminated bool
 }
 
 // runDebugSessionWithBPs is a helper for executing the common init and shutdown
@@ -2480,7 +2584,16 @@ func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cm
 		client.ExpectStoppedEvent(t)
 		onBP.execute()
 		if onBP.disconnect {
+			if onBP.terminated {
+				client.ExpectTerminatedEvent(t)
+			}
 			client.DisconnectRequestWithKillOption(true)
+			if onBP.terminated {
+				client.ExpectOutputEventProcessExited(t, 0)
+				client.ExpectOutputEventDetaching(t)
+			} else {
+				client.ExpectOutputEventDetachingKill(t)
+			}
 			client.ExpectDisconnectResponse(t)
 			return
 		}
@@ -2493,6 +2606,12 @@ func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cm
 		client.ExpectTerminatedEvent(t)
 	}
 	client.DisconnectRequestWithKillOption(true)
+	if cmd == "launch" {
+		client.ExpectOutputEventProcessExited(t, 0)
+		client.ExpectOutputEventDetaching(t)
+	} else if cmd == "attach" {
+		client.ExpectOutputEventDetachingKill(t)
+	}
 	client.ExpectDisconnectResponse(t)
 }
 
@@ -2508,13 +2627,14 @@ func TestLaunchDebugRequest(t *testing.T) {
 	r, w, _ := os.Pipe()
 	os.Stderr = w
 
+	tmpBin := "__tmpBin"
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 		// We reuse the harness that builds, but ignore the built binary,
 		// only relying on the source to be built in response to LaunchRequest.
 		runDebugSession(t, client, "launch", func() {
 			wd, _ := os.Getwd()
 			client.LaunchRequestWithArgs(map[string]interface{}{
-				"mode": "debug", "program": fixture.Source, "output": filepath.Join(wd, "__mybin")})
+				"mode": "debug", "program": fixture.Source, "output": filepath.Join(wd, tmpBin)})
 		}, fixture.Source)
 	})
 	// Wait for the test to finish to capture all stderr
@@ -2536,6 +2656,12 @@ func TestLaunchDebugRequest(t *testing.T) {
 		if runtime.GOOS != "windows" || !strings.Contains(rmErr, "Access is denied") {
 			t.Fatalf("Binary removal failure:\n%s\n", rmErr)
 		}
+	} else {
+		// We did not get a removal error, but did we even try to remove before exiting?
+		// Confirm that the binary did get removed.
+		if _, err := os.Stat(tmpBin); err == nil || os.IsExist(err) {
+			t.Fatal("Failed to remove temp binary", tmpBin)
+		}
 	}
 }
 
@@ -2572,21 +2698,33 @@ func TestLaunchRequestDefaults(t *testing.T) {
 	})
 }
 
-func TestLaunchRequestNoDebug(t *testing.T) {
+func TestLaunchRequestNoDebug_GoodStatus(t *testing.T) {
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 		runNoDebugDebugSession(t, client, func() {
 			client.LaunchRequestWithArgs(map[string]interface{}{
 				"noDebug": true,
-				"mode":    "", /*"debug" by default*/
+				"mode":    "debug",
+				"program": fixture.Source,
+				"output":  cleanExeName("__mybin")})
+		}, fixture.Source, []int{8}, 0)
+	})
+}
+
+func TestLaunchRequestNoDebug_BadStatus(t *testing.T) {
+	runTest(t, "issue1101", func(client *daptest.Client, fixture protest.Fixture) {
+		runNoDebugDebugSession(t, client, func() {
+			client.LaunchRequestWithArgs(map[string]interface{}{
+				"noDebug": true,
+				"mode":    "debug",
 				"program": fixture.Source,
 				"output":  cleanExeName("__mybin")})
-		}, fixture.Source, []int{8})
+		}, fixture.Source, []int{8}, 2)
 	})
 }
 
 // runNoDebugDebugSession tests the session started with noDebug=true runs uninterrupted
 // even when breakpoint is set.
-func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmdRequest func(), source string, breakpoints []int) {
+func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmdRequest func(), source string, breakpoints []int, status int) {
 	client.InitializeRequest()
 	client.ExpectInitializeResponse(t)
 
@@ -2595,6 +2733,7 @@ func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmdRequest fun
 	// noDebug mode applies only to "launch" requests.
 	client.ExpectLaunchResponse(t)
 
+	client.ExpectOutputEventProcessExited(t, status)
 	client.ExpectTerminatedEvent(t)
 	client.DisconnectRequestWithKillOption(true)
 	client.ExpectDisconnectResponse(t)
@@ -2652,13 +2791,7 @@ func TestAttachRequest(t *testing.T) {
 	}
 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 		// Start the program to attach to
-		// TODO(polina): do I need to sanity check testBackend and runtime.GOOS?
-		cmd := exec.Command(fixture.Path)
-		cmd.Stdout = os.Stdout
-		cmd.Stderr = os.Stderr
-		if err := cmd.Start(); err != nil {
-			t.Fatal(err)
-		}
+		cmd := execFixture(t, fixture)
 
 		runDebugSessionWithBPs(t, client, "attach",
 			// Attach
@@ -2884,6 +3017,21 @@ func TestBadLaunchRequests(t *testing.T) {
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: 'buildFlags' attribute '123' in debug configuration is not a string.")
 
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": 123})
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: 'substitutePath' attribute '123' in debug configuration is not a []{'from': string, 'to': string}")
+
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{123}})
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: 'substitutePath' attribute '[123]' in debug configuration is not a []{'from': string, 'to': string}")
+
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"to": "path2"}}})
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: 'substitutePath' attribute '[map[to:path2]]' in debug configuration is not a []{'from': string, 'to': string}")
+
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"from": "path1", "to": 123}}})
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: 'substitutePath' attribute '[map[from:path1 to:123]]' in debug configuration is not a []{'from': string, 'to': string}")
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "wd": 123})
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: 'wd' attribute '123' in debug configuration is not a string.")
diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go
index 2e77edb2..740c09d4 100644
--- a/service/test/integration1_test.go
+++ b/service/test/integration1_test.go
@@ -813,18 +813,18 @@ func Test1Issue355(t *testing.T) {
 		state = <-ch
 		assertError(state.Err, t, "Continue()")
 
-		_, err = c.Next()
-		assertError(err, t, "Next()")
-		_, err = c.Step()
-		assertError(err, t, "Step()")
-		_, err = c.StepInstruction()
-		assertError(err, t, "StepInstruction()")
-		_, err = c.SwitchThread(tid)
-		assertError(err, t, "SwitchThread()")
-		_, err = c.SwitchGoroutine(gid)
-		assertError(err, t, "SwitchGoroutine()")
-		_, err = c.Halt()
-		assertError(err, t, "Halt()")
+		s, err := c.Next()
+		assertErrorOrExited(s, err, t, "Next()")
+		s, err = c.Step()
+		assertErrorOrExited(s, err, t, "Step()")
+		s, err = c.StepInstruction()
+		assertErrorOrExited(s, err, t, "StepInstruction()")
+		s, err = c.SwitchThread(tid)
+		assertErrorOrExited(s, err, t, "SwitchThread()")
+		s, err = c.SwitchGoroutine(gid)
+		assertErrorOrExited(s, err, t, "SwitchGoroutine()")
+		s, err = c.Halt()
+		assertErrorOrExited(s, err, t, "Halt()")
 		_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1})
 		assertError(err, t, "CreateBreakpoint()")
 		_, err = c.ClearBreakpoint(bp.ID)
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 51502c3f..e7885b7f 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1095,6 +1095,16 @@ func TestClientServer_FullStacktrace(t *testing.T) {
 	})
 }
 
+func assertErrorOrExited(s *api.DebuggerState, err error, t *testing.T, reason string) {
+	if err != nil {
+		return
+	}
+	if s != nil && s.Exited {
+		return
+	}
+	t.Fatalf("%s (no error and no exited status)", reason)
+}
+
 func TestIssue355(t *testing.T) {
 	// After the target process has terminated should return an error but not crash
 	protest.AllowRecording(t)
@@ -1116,18 +1126,18 @@ func TestIssue355(t *testing.T) {
 		state = <-ch
 		assertError(state.Err, t, "Continue()")
 
-		_, err = c.Next()
-		assertError(err, t, "Next()")
-		_, err = c.Step()
-		assertError(err, t, "Step()")
-		_, err = c.StepInstruction()
-		assertError(err, t, "StepInstruction()")
-		_, err = c.SwitchThread(tid)
-		assertError(err, t, "SwitchThread()")
-		_, err = c.SwitchGoroutine(gid)
-		assertError(err, t, "SwitchGoroutine()")
-		_, err = c.Halt()
-		assertError(err, t, "Halt()")
+		s, err := c.Next()
+		assertErrorOrExited(s, err, t, "Next()")
+		s, err = c.Step()
+		assertErrorOrExited(s, err, t, "Step()")
+		s, err = c.StepInstruction()
+		assertErrorOrExited(s, err, t, "StepInstruction()")
+		s, err = c.SwitchThread(tid)
+		assertErrorOrExited(s, err, t, "SwitchThread()")
+		s, err = c.SwitchGoroutine(gid)
+		assertErrorOrExited(s, err, t, "SwitchGoroutine()")
+		s, err = c.Halt()
+		assertErrorOrExited(s, err, t, "Halt()")
 		_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1})
 		if testBackend != "rr" {
 			assertError(err, t, "CreateBreakpoint()")
@@ -2277,3 +2287,34 @@ func TestDetachLeaveRunning(t *testing.T) {
 	defer server.Stop()
 	assertNoError(client.Detach(false), t, "Detach")
 }
+
+func assertNoDuplicateBreakpoints(t *testing.T, c service.Client) {
+	t.Helper()
+	bps, _ := c.ListBreakpoints()
+	seen := make(map[int]bool)
+	for _, bp := range bps {
+		t.Logf("%#v\n", bp)
+		if seen[bp.ID] {
+			t.Fatalf("duplicate breakpoint ID %d", bp.ID)
+		}
+		seen[bp.ID] = true
+	}
+}
+
+func TestToggleBreakpointRestart(t *testing.T) {
+	// Checks that breakpoints IDs do not overlap after Restart if there are disabled breakpoints.
+	withTestClient2("testtoggle", t, func(c service.Client) {
+		bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint"})
+		assertNoError(err, t, "CreateBreakpoint 1")
+		_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 2, Name: "secondbreakpoint"})
+		assertNoError(err, t, "CreateBreakpoint 2")
+		_, err = c.ToggleBreakpoint(bp1.ID)
+		assertNoError(err, t, "ToggleBreakpoint")
+		_, err = c.Restart(false)
+		assertNoError(err, t, "Restart")
+		assertNoDuplicateBreakpoints(t, c)
+		_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 3, Name: "thirdbreakpoint"})
+		assertNoError(err, t, "CreateBreakpoint 3")
+		assertNoDuplicateBreakpoints(t, c)
+	})
+}