Repository: go-delve/delve
Pull request #2425 (2021-04-08 15:18:31)

service: fix breakpoint IDs after Restart with disabled breakpoints
When restarting we must take care of setting breakpoint IDs correctly
so that enabled breakpoints do not end up having the same ID as a
disabled breakpoint, also we must make sure that breakpoints created
after restart will not get an ID already used by a disabled breakpoint.



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/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..ef3932ac 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -147,6 +147,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 {
@@ -169,6 +170,7 @@ func (s *Server) Stop() {
 	} else {
 		s.stopNoDebugProcess()
 	}
+	s.log.Debug("DAP server stopped")
 }
 
 // signalDisconnect closes config.DisconnectChan if not nil, which
@@ -421,6 +423,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)}
@@ -568,9 +579,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,6 +588,7 @@ 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
@@ -613,8 +624,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 +652,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)
+				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)
+				exited = err
+				s.logToConsole(exited.Error())
 			default:
 				s.log.Error(err)
 			}
@@ -664,6 +695,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()
@@ -1272,7 +1308,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
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index af306043..4963ecf9 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
 }
 
@@ -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 a99bf4cd..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"
@@ -170,6 +171,35 @@ func (c *Client) ExpectOutputEvent(t *testing.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.ExpectMessage(t).(*dap.ConfigurationDoneResponse)
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 73cd7e4b..6bc77407 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() {
@@ -2019,8 +2036,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,
 			}})
 	})
@@ -2428,9 +2445,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
@@ -2475,7 +2495,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
 		}
@@ -2488,6 +2517,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)
 }
 
@@ -2567,21 +2602,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)
 
@@ -2590,6 +2637,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)
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 51502c3f..aae5ac9a 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -2277,3 +2277,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)
+	})
+}