Repository: go-delve/delve
Pull request #2480 (2021-05-14 17:33:25)

service/dap: send 'continued' event on next/step/stepout requests
Next/step/stepout requests assume that execution is only resumed
on the thread that was specified. We cannot resume execution on
only a single thread, so we need to send a continued event to let
the client know that all threads have resumed.

suzmue: Before:
![Screen Shot 2021-05-14 at 12 12 51 PM](https://user-images.githubusercontent.com/6634754/118307668-0160c280-b4b9-11eb-815d-5187eec76c4b.png)
After:
![Screen Shot 2021-05-14 at 12 18 42 PM](https://user-images.githubusercontent.com/6634754/118307664-00c82c00-b4b9-11eb-89fa-198393c0e6c6.png)

suzmue: The TestContinueOnEntry failure is unrelated (it looks like a similar bug to TestPreSetBreakpoint)
derekparker: Before I merge @suzmue could you please rebase on master since https://github.com/go-delve/delve/pull/2489 has been merged and should fix the only failing test in this build.

Patch:
diff --git a/pkg/dwarf/op/op.go b/pkg/dwarf/op/op.go
index 7410f7c1..1d19bc10 100644
--- a/pkg/dwarf/op/op.go
+++ b/pkg/dwarf/op/op.go
@@ -30,12 +30,20 @@ type context struct {
 
 // Piece is a piece of memory stored either at an address or in a register.
 type Piece struct {
-	Size       int
-	Addr       int64
-	RegNum     uint64
-	IsRegister bool
+	Size int
+	Kind PieceKind
+	Val  uint64
 }
 
+// PieceKind describes the kind of a piece.
+type PieceKind uint8
+
+const (
+	AddrPiece PieceKind = iota // The piece is stored in memory, Val is the address
+	RegPiece                   // The piece is stored in a register, Val is the register number
+	ImmPiece                   // The piece is an immediate value, Val is the value
+)
+
 // ExecuteStackProgram executes a DWARF location expression and returns
 // either an address (int64), or a slice of Pieces for location expressions
 // that don't evaluate to an address (such as register and composite expressions).
@@ -56,7 +64,7 @@ func ExecuteStackProgram(regs DwarfRegisters, instructions []byte, ptrSize int)
 		if ctxt.reg && opcode != DW_OP_piece {
 			// last opcode was DW_OP_regN and next one isn't DW_OP_piece so convert
 			// the register piece into a stack value.
-			ctxt.stack = append(ctxt.stack, int64(regs.Uint64Val(ctxt.pieces[len(ctxt.pieces)-1].RegNum)))
+			ctxt.stack = append(ctxt.stack, int64(regs.Uint64Val(ctxt.pieces[len(ctxt.pieces)-1].Val)))
 			ctxt.pieces = ctxt.pieces[:len(ctxt.pieces)-1]
 			ctxt.reg = false
 		}
@@ -72,8 +80,8 @@ func ExecuteStackProgram(regs DwarfRegisters, instructions []byte, ptrSize int)
 	}
 
 	if ctxt.pieces != nil {
-		if len(ctxt.pieces) == 1 && ctxt.pieces[0].IsRegister {
-			return int64(regs.Uint64Val(ctxt.pieces[0].RegNum)), ctxt.pieces, nil
+		if len(ctxt.pieces) == 1 && ctxt.pieces[0].Kind == RegPiece {
+			return int64(regs.Uint64Val(ctxt.pieces[0].Val)), ctxt.pieces, nil
 		}
 		return 0, ctxt.pieces, nil
 	}
@@ -187,9 +195,9 @@ func register(opcode Opcode, ctxt *context) error {
 	ctxt.reg = true
 	if opcode == DW_OP_regx {
 		n, _ := util.DecodeSLEB128(ctxt.buf)
-		ctxt.pieces = append(ctxt.pieces, Piece{IsRegister: true, RegNum: uint64(n)})
+		ctxt.pieces = append(ctxt.pieces, Piece{Kind: RegPiece, Val: uint64(n)})
 	} else {
-		ctxt.pieces = append(ctxt.pieces, Piece{IsRegister: true, RegNum: uint64(opcode - DW_OP_reg0)})
+		ctxt.pieces = append(ctxt.pieces, Piece{Kind: RegPiece, Val: uint64(opcode - DW_OP_reg0)})
 	}
 	return nil
 }
@@ -203,11 +211,14 @@ func piece(opcode Opcode, ctxt *context) error {
 	}
 
 	if len(ctxt.stack) == 0 {
-		return errors.New("empty OP stack")
+		// nothing on the stack means this piece is unavailable (padding,
+		// optimized away...), see DWARFv4 sec. 2.6.1.3 page 30.
+		ctxt.pieces = append(ctxt.pieces, Piece{Size: int(sz), Kind: ImmPiece, Val: 0})
+		return nil
 	}
 
 	addr := ctxt.stack[len(ctxt.stack)-1]
-	ctxt.pieces = append(ctxt.pieces, Piece{Size: int(sz), Addr: addr})
+	ctxt.pieces = append(ctxt.pieces, Piece{Size: int(sz), Kind: AddrPiece, Val: uint64(addr)})
 	ctxt.stack = ctxt.stack[:0]
 	return nil
 }
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index 1313a514..fa35095d 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -3,7 +3,6 @@ package proc
 import (
 	"bytes"
 	"debug/dwarf"
-	"encoding/binary"
 	"errors"
 	"fmt"
 	"go/ast"
@@ -905,17 +904,14 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
 }
 
 func convertInt(n uint64, signed bool, size int64) uint64 {
-	buf := make([]byte, 64/8)
-	binary.BigEndian.PutUint64(buf, n)
-	m := 64/8 - int(size)
-	s := byte(0)
-	if signed && (buf[m]&0x80 > 0) {
-		s = 0xff
+	bits := uint64(size) * 8
+	mask := uint64((1 << bits) - 1)
+	r := n & mask
+	if signed && (r>>(bits-1)) != 0 {
+		// sign extension
+		r |= ^uint64(0) &^ mask
 	}
-	for i := 0; i < m; i++ {
-		buf[i] = s
-	}
-	return uint64(binary.BigEndian.Uint64(buf))
+	return r
 }
 
 func (scope *EvalScope) evalBuiltinCall(node *ast.CallExpr) (*Variable, error) {
@@ -1636,8 +1632,15 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
 
 		r := xv.newVariable("", 0, typ, scope.Mem)
 		r.Value = rc
-		if r.Kind == reflect.String {
+		switch r.Kind {
+		case reflect.String:
 			r.Len = xv.Len + yv.Len
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+			n, _ := constant.Int64Val(r.Value)
+			r.Value = constant.MakeInt64(int64(convertInt(uint64(n), true, typ.Size())))
+		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+			n, _ := constant.Uint64Val(r.Value)
+			r.Value = constant.MakeUint64(convertInt(n, false, typ.Size()))
 		}
 		return r, nil
 	}
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index 176ccf9f..37743667 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -1556,7 +1556,8 @@ func (t *gdbThread) reloadRegisters() error {
 
 	if t.p.gcmdok {
 		if err := t.p.conn.readRegisters(t.strID, t.regs.buf); err != nil {
-			if isProtocolErrorUnsupported(err) {
+			gdberr, isProt := err.(*GdbProtocolError)
+			if isProtocolErrorUnsupported(err) || (t.p.conn.isDebugserver && isProt && gdberr.code == "E74") {
 				t.p.gcmdok = false
 			} else {
 				return err
diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go
index 70ff9434..8b19fbfb 100644
--- a/pkg/proc/mem.go
+++ b/pkg/proc/mem.go
@@ -1,6 +1,7 @@
 package proc
 
 import (
+	"encoding/binary"
 	"errors"
 	"fmt"
 
@@ -99,22 +100,33 @@ func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters
 	cmem := &compositeMemory{realmem: mem, arch: arch, regs: regs, pieces: pieces, data: []byte{}}
 	for i := range pieces {
 		piece := &pieces[i]
-		if piece.IsRegister {
-			reg := regs.Bytes(piece.RegNum)
+		switch piece.Kind {
+		case op.RegPiece:
+			reg := regs.Bytes(piece.Val)
 			if piece.Size == 0 && len(pieces) == 1 {
 				piece.Size = len(reg)
 			}
 			if piece.Size > len(reg) {
 				if regs.FloatLoadError != nil {
-					return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d), also error loading floating point registers: %v", piece.Size, piece.RegNum, len(reg), regs.FloatLoadError)
+					return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d), also error loading floating point registers: %v", piece.Size, piece.Val, len(reg), regs.FloatLoadError)
 				}
-				return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", piece.Size, piece.RegNum, len(reg))
+				return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", piece.Size, piece.Val, len(reg))
 			}
 			cmem.data = append(cmem.data, reg[:piece.Size]...)
-		} else {
+		case op.AddrPiece:
 			buf := make([]byte, piece.Size)
-			mem.ReadMemory(buf, uint64(piece.Addr))
+			mem.ReadMemory(buf, uint64(piece.Val))
 			cmem.data = append(cmem.data, buf...)
+		case op.ImmPiece:
+			sz := 8
+			if piece.Size > sz {
+				sz = piece.Size
+			}
+			buf := make([]byte, sz)
+			binary.LittleEndian.PutUint64(buf, piece.Val)
+			cmem.data = append(cmem.data, buf[:piece.Size]...)
+		default:
+			panic("unsupported piece kind")
 		}
 	}
 	return cmem, nil
@@ -147,17 +159,22 @@ func (mem *compositeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
 			// changed memory interval overlaps current piece
 			pieceMem := mem.data[curAddr : curAddr+uint64(piece.Size)]
 
-			if piece.IsRegister {
-				err := mem.regs.ChangeFunc(piece.RegNum, op.DwarfRegisterFromBytes(pieceMem))
+			switch piece.Kind {
+			case op.RegPiece:
+				err := mem.regs.ChangeFunc(piece.Val, op.DwarfRegisterFromBytes(pieceMem))
 				if err != nil {
 					return donesz, err
 				}
-			} else {
-				n, err := mem.realmem.WriteMemory(uint64(piece.Addr), pieceMem)
+			case op.AddrPiece:
+				n, err := mem.realmem.WriteMemory(uint64(piece.Val), pieceMem)
 				if err != nil {
 					return donesz + n, err
 				}
-
+			case op.ImmPiece:
+				//TODO(aarzilli): maybe return an error if the user tried to change the value?
+				// nothing to do
+			default:
+				panic("unsupported piece kind")
 			}
 			donesz += piece.Size
 		}
diff --git a/service/dap/error_ids.go b/service/dap/error_ids.go
index 1a5344df..2ae0f330 100644
--- a/service/dap/error_ids.go
+++ b/service/dap/error_ids.go
@@ -21,7 +21,8 @@ const (
 	UnableToListGlobals        = 2007
 	UnableToLookupVariable     = 2008
 	UnableToEvaluateExpression = 2009
-	UnableToGetExceptionInfo   = 2010
+	UnableToHalt               = 2010
+	UnableToGetExceptionInfo   = 2011
 	// Add more codes as we support more requests
 	DebuggeeIsRunning = 4000
 	DisconnectError   = 5000
diff --git a/service/dap/server.go b/service/dap/server.go
index 0d808558..2e108cd8 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -377,7 +377,7 @@ func (s *Server) handleRequest(request dap.Message) {
 		return
 	}
 
-	// These requests, can be handeled regardless of whether the targret is running
+	// These requests, can be handled regardless of whether the targret is running
 	switch request := request.(type) {
 	case *dap.DisconnectRequest:
 		// Required
@@ -385,7 +385,6 @@ func (s *Server) handleRequest(request dap.Message) {
 		return
 	case *dap.PauseRequest:
 		// Required
-		// TODO: implement this request in V0
 		s.onPauseRequest(request)
 		return
 	case *dap.TerminateRequest:
@@ -1255,6 +1254,15 @@ func stoppedGoroutineID(state *api.DebuggerState) (id int) {
 // due to an error, so the server is ready to receive new requests.
 func (s *Server) doStepCommand(command string, threadId int, asyncSetupDone chan struct{}) {
 	defer s.asyncCommandDone(asyncSetupDone)
+	// All of the threads will be continued by this request, so we need to send
+	// a continued event so the UI can properly reflect the current state.
+	s.send(&dap.ContinuedEvent{
+		Event: *newEvent("continued"),
+		Body: dap.ContinuedEventBody{
+			ThreadId:            threadId,
+			AllThreadsContinued: true,
+		},
+	})
 	_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.SwitchGoroutine, GoroutineID: threadId}, nil)
 	if err != nil {
 		s.log.Errorf("Error switching goroutines while stepping: %v", err)
@@ -1275,10 +1283,20 @@ func (s *Server) doStepCommand(command string, threadId int, asyncSetupDone chan
 	s.doRunCommand(command, asyncSetupDone)
 }
 
-// onPauseRequest sends a not-yet-implemented error response.
+// onPauseRequest handles 'pause' request.
 // This is a mandatory request to support.
-func (s *Server) onPauseRequest(request *dap.PauseRequest) { // TODO V0
-	s.sendNotYetImplementedErrorResponse(request.Request)
+func (s *Server) onPauseRequest(request *dap.PauseRequest) {
+	_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
+	if err != nil {
+		s.sendErrorResponse(request.Request, UnableToHalt, "Unable to halt execution", err.Error())
+		return
+	}
+	s.send(&dap.PauseResponse{Response: *newResponse(request.Request)})
+	// No need to send any event here.
+	// If we received this request while stopped, there already was an event for the stop.
+	// If we received this while running, then doCommand will unblock and trigger the right
+	// event, using debugger.StopReason because manual stop reason always wins even if we
+	// simultaneously receive a manual stop request and hit a breakpoint.
 }
 
 // stackFrame represents the index of a frame within
@@ -2089,7 +2107,11 @@ func (s *Server) doRunCommand(command string, asyncSetupDone chan struct{}) {
 	}
 
 	stopReason := s.debugger.StopReason()
-	s.log.Debugf("%q command stopped - reason %q", command, stopReason)
+	file, line := "?", -1
+	if state != nil && state.CurrentThread != nil {
+		file, line = state.CurrentThread.File, state.CurrentThread.Line
+	}
+	s.log.Debugf("%q command stopped - reason %q, location %s:%d", command, stopReason, file, line)
 
 	s.resetHandlesForStoppedEvent()
 	stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
@@ -2106,7 +2128,7 @@ func (s *Server) doRunCommand(command string, asyncSetupDone chan struct{}) {
 			stopped.Body.Reason = "step"
 		case proc.StopManual: // triggered by halt
 			stopped.Body.Reason = "pause"
-		case proc.StopUnknown: // can happen while stopping
+		case proc.StopUnknown: // can happen while terminating
 			stopped.Body.Reason = "unknown"
 		case proc.StopWatchpoint:
 			stopped.Body.Reason = "data breakpoint"


Test patch:
diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go
index ed514c64..51860728 100644
--- a/pkg/proc/dwarf_expr_test.go
+++ b/pkg/proc/dwarf_expr_test.go
@@ -152,9 +152,11 @@ func TestDwarfExprRegisters(t *testing.T) {
 
 func TestDwarfExprComposite(t *testing.T) {
 	testCases := map[string]uint16{
-		"pair.k": 0x8765,
-		"pair.v": 0x5678,
-		"n":      42,
+		"pair.k":  0x8765,
+		"pair.v":  0x5678,
+		"n":       42,
+		"pair2.k": 0x8765,
+		"pair2.v": 0,
 	}
 
 	const stringVal = "this is a string"
@@ -188,6 +190,9 @@ func TestDwarfExprComposite(t *testing.T) {
 		op.DW_OP_reg1, op.DW_OP_piece, uint(8),
 		op.DW_OP_reg0, op.DW_OP_piece, uint(8)))
 	dwb.AddVariable("n", intoff, dwarfbuilder.LocationBlock(op.DW_OP_reg3))
+	dwb.AddVariable("pair2", pairoff, dwarfbuilder.LocationBlock(
+		op.DW_OP_reg2, op.DW_OP_piece, uint(2),
+		op.DW_OP_piece, uint(2)))
 	dwb.TagClose()
 
 	bi, _ := fakeBinaryInfo(t, dwb)
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 99ecbca7..6241a1b9 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -5169,9 +5169,9 @@ func TestCompositeMemoryWrite(t *testing.T) {
 		oldPc, oldRax, oldXmm1 := getregs()
 		t.Logf("PC %#x AX %#x XMM1 %#x", oldPc, oldRax, oldXmm1)
 
-		memRax, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, RegNum: 0, IsRegister: true}})
+		memRax, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 0, Kind: op.RegPiece}})
 		assertNoError(err, t, "NewCompositeMemory (rax)")
-		memXmm1, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, RegNum: 18, IsRegister: true}})
+		memXmm1, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 18, Kind: op.RegPiece}})
 		assertNoError(err, t, "NewCompositeMemory (xmm1)")
 
 		if memRax := getmem(memRax); memRax != oldRax {
diff --git a/pkg/proc/proc_unexported_test.go b/pkg/proc/proc_unexported_test.go
index 62327ea8..ce269475 100644
--- a/pkg/proc/proc_unexported_test.go
+++ b/pkg/proc/proc_unexported_test.go
@@ -73,3 +73,26 @@ func TestAlignAddr(t *testing.T) {
 		c(example.align, example.in+0x10000, example.tgt+0x10000)
 	}
 }
+
+func TestConvertInt(t *testing.T) {
+	var testCases = []struct {
+		in     uint64
+		signed bool
+		size   int64
+		tgt    uint64
+	}{
+		{1, false, 1, 1},
+		{uint64(0xf0), true, 1, 0xfffffffffffffff0},
+		{uint64(0xf0), false, 1, 0xf0},
+		{uint64(0x70), true, 1, 0x70},
+		{uint64(0x90f0), true, 2, 0xffffffffffff90f0},
+		{uint64(0x90f0), false, 2, 0x90f0},
+	}
+	for _, tc := range testCases {
+		out := convertInt(tc.in, tc.signed, tc.size)
+		t.Logf("in=%#016x signed=%v size=%d -> %#016x\n", tc.in, tc.signed, tc.size, out)
+		if out != tc.tgt {
+			t.Errorf("expected=%#016x got=%#016x\n", tc.tgt, out)
+		}
+	}
+}
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index b58bfad7..52221adc 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -285,9 +285,9 @@ func (c *Client) StepOutRequest(thread int) {
 }
 
 // PauseRequest sends a 'pause' request.
-func (c *Client) PauseRequest() {
-	request := &dap.NextRequest{Request: *c.newRequest("pause")}
-	// TODO(polina): arguments
+func (c *Client) PauseRequest(threadId int) {
+	request := &dap.PauseRequest{Request: *c.newRequest("pause")}
+	request.Arguments.ThreadId = threadId
 	c.send(request)
 }
 
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 64a2ed75..6175a0af 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -572,6 +572,20 @@ func TestPreSetBreakpoint(t *testing.T) {
 		// "Continue" is triggered after the response is sent
 
 		client.ExpectTerminatedEvent(t)
+
+		// Pause request after termination should result in an error.
+		// But in certain cases this request actually succeeds.
+		client.PauseRequest(1)
+		switch r := client.ExpectMessage(t).(type) {
+		case *dap.ErrorResponse:
+			if r.Message != "Unable to halt execution" {
+				t.Errorf("\ngot  %#v\nwant Message='Unable to halt execution'", r)
+			}
+		case *dap.PauseResponse:
+		default:
+			t.Fatalf("Unexpected response type: expect error or pause, got %#v", r)
+		}
+
 		client.DisconnectRequest()
 		client.ExpectOutputEventProcessExited(t, 0)
 		client.ExpectOutputEventDetaching(t)
@@ -1687,6 +1701,7 @@ func TestVariablesLoading(t *testing.T) {
 					// step into another function
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
+					client.ExpectContinuedEvent(t)
 					client.ExpectStoppedEvent(t)
 					handleStop(t, client, 1, "main.barfoo", 24)
 					loadvars(1001 /*second frame here is same as topmost above*/)
@@ -1734,6 +1749,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
 					// Step into pkg.AnotherMethod()
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
+					client.ExpectContinuedEvent(t)
 					client.ExpectStoppedEvent(t)
 
 					client.StackTraceRequest(1, 0, 20)
@@ -1967,6 +1983,7 @@ func TestSetBreakpointWhileRunning(t *testing.T) {
 					// We can set breakpoints while nexting
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 					client.SetBreakpointsRequest(fixture.Source, []int{15}) // [16,] => [15,]
 					se, br := expectSetBreakpointsResponseAndStoppedEvent(t, client)
 					if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
@@ -2465,18 +2482,22 @@ func TestNextAndStep(t *testing.T) {
 
 					client.StepOutRequest(1)
 					client.ExpectStepOutResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStop("main.main", 18)
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStop("main.main", 19)
 
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStop("main.inlineThis", 5)
 
 					client.NextRequest(-1000)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 					if se := client.ExpectStoppedEvent(t); se.Body.Reason != "error" || se.Body.Text != "unknown goroutine -1000" {
 						t.Errorf("got %#v, want Reaspon=\"error\", Text=\"unknown goroutine -1000\"", se)
 					}
@@ -2505,6 +2526,7 @@ func TestNextParked(t *testing.T) {
 
 					client.NextRequest(goroutineId)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 
 					se := client.ExpectStoppedEvent(t)
 					if se.Body.ThreadId != goroutineId {
@@ -2534,6 +2556,7 @@ func TestStepInParked(t *testing.T) {
 
 					client.StepInRequest(goroutineId)
 					client.ExpectStepInResponse(t)
+					client.ExpectContinuedEvent(t)
 
 					se := client.ExpectStoppedEvent(t)
 					if se.Body.ThreadId != goroutineId {
@@ -2653,6 +2676,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
 					}
 					client.StepOutRequest(goroutineId)
 					client.ExpectStepOutResponse(t)
+					client.ExpectContinuedEvent(t)
 
 					switch e := client.ExpectMessage(t).(type) {
 					case *dap.StoppedEvent:
@@ -2706,18 +2730,22 @@ func TestBadAccess(t *testing.T) {
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("invalid memory address or nil pointer dereference")
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("next while nexting")
 
 					client.StepInRequest(1)
 					client.ExpectStepInResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("next while nexting")
 
 					client.StepOutRequest(1)
 					client.ExpectStepOutResponse(t)
+					client.ExpectContinuedEvent(t)
 					expectStoppedOnError("next while nexting")
 				},
 				disconnect: true,
@@ -2778,6 +2806,7 @@ func TestPanicBreakpointOnNext(t *testing.T) {
 
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
+					client.ExpectContinuedEvent(t)
 
 					se := client.ExpectStoppedEvent(t)
 					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "Paused on panic" {
@@ -2821,15 +2850,8 @@ func TestFatalThrowBreakpoint(t *testing.T) {
 	})
 }
 
-// handleStop covers the standard sequence of reqeusts issued by
-// a client at a breakpoint or another non-terminal stop event.
-// The details have been tested by other tests,
-// so this is just a sanity check.
-// Skips line check if line is -1.
-func handleStop(t *testing.T, client *daptest.Client, thread int, name string, line int) {
+func verifyStopLocation(t *testing.T, client *daptest.Client, thread int, name string, line int) {
 	t.Helper()
-	client.ThreadsRequest()
-	client.ExpectThreadsResponse(t)
 
 	client.StackTraceRequest(thread, 0, 20)
 	st := client.ExpectStackTraceResponse(t)
@@ -2843,6 +2865,19 @@ func handleStop(t *testing.T, client *daptest.Client, thread int, name string, l
 			t.Errorf("\ngot  %#v\nwant Name=%q", st, name)
 		}
 	}
+}
+
+// handleStop covers the standard sequence of requests issued by
+// a client at a breakpoint or another non-terminal stop event.
+// The details have been tested by other tests,
+// so this is just a sanity check.
+// Skips line check if line is -1.
+func handleStop(t *testing.T, client *daptest.Client, thread int, name string, line int) {
+	t.Helper()
+	client.ThreadsRequest()
+	client.ExpectThreadsResponse(t)
+
+	verifyStopLocation(t, client, thread, name, line)
 
 	client.ScopesRequest(1000)
 	client.ExpectScopesResponse(t)
@@ -3140,6 +3175,51 @@ func TestAttachRequest(t *testing.T) {
 	})
 }
 
+func TestPauseAndContinue(t *testing.T) {
+	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{6},
+			[]onBreakpoint{{
+				execute: func() {
+					verifyStopLocation(t, client, 1, "main.loop", 6)
+
+					// Continue resumes all goroutines, so thread id is ignored
+					client.ContinueRequest(12345)
+					client.ExpectContinueResponse(t)
+
+					time.Sleep(time.Second)
+
+					// Halt pauses all goroutines, so thread id is ignored
+					client.PauseRequest(56789)
+					// Since we are in async mode while running, we might receive next two messages in either order.
+					for i := 0; i < 2; i++ {
+						msg := client.ExpectMessage(t)
+						switch m := msg.(type) {
+						case *dap.StoppedEvent:
+							if m.Body.Reason != "pause" || m.Body.ThreadId != 0 && m.Body.ThreadId != 1 {
+								t.Errorf("\ngot %#v\nwant ThreadId=0/1 Reason='pause'", m)
+							}
+						case *dap.PauseResponse:
+						default:
+							t.Fatalf("got %#v, want StoppedEvent or PauseResponse", m)
+						}
+					}
+
+					// Pause will be a no-op at a pause: there will be no additional stopped events
+					client.PauseRequest(1)
+					client.ExpectPauseResponse(t)
+				},
+				// The program has an infinite loop, so we must kill it by disconnecting.
+				disconnect: true,
+			}})
+	})
+}
+
 func TestUnupportedCommandResponses(t *testing.T) {
 	var got *dap.ErrorResponse
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
@@ -3188,24 +3268,6 @@ func TestUnupportedCommandResponses(t *testing.T) {
 	})
 }
 
-func TestRequiredNotYetImplementedResponses(t *testing.T) {
-	var got *dap.ErrorResponse
-	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
-		seqCnt := 1
-		expectNotYetImplemented := func(cmd string) {
-			t.Helper()
-			got = client.ExpectNotYetImplementedErrorResponse(t)
-			if got.RequestSeq != seqCnt || got.Command != cmd {
-				t.Errorf("\ngot  %#v\nwant RequestSeq=%d Command=%s", got, seqCnt, cmd)
-			}
-			seqCnt++
-		}
-
-		client.PauseRequest()
-		expectNotYetImplemented("pause")
-	})
-}
-
 func TestOptionalNotYetImplementedResponses(t *testing.T) {
 	var got *dap.ErrorResponse
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index 4bab82a9..a40e9710 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -836,6 +836,10 @@ func TestEvalExpression(t *testing.T) {
 		{`iface2map.(data)`, false, "…", "…", "map[string]interface {}", nil},
 
 		{"issue1578", false, "main.Block {cache: *main.Cache nil}", "main.Block {cache: *main.Cache nil}", "main.Block", nil},
+		{"ni8 << 2", false, "-20", "-20", "int8", nil},
+		{"ni8 << 8", false, "0", "0", "int8", nil},
+		{"ni8 >> 1", false, "-3", "-3", "int8", nil},
+		{"bytearray[0] * bytearray[0]", false, "144", "144", "uint8", nil},
 	}
 
 	ver, _ := goversion.Parse(runtime.Version())