Repository: go-delve/delve
Pull request #3004 (2022-05-08 15:29:48)

Add auto-complete for local variables
Add auto-complete for local variables when using `print` or `whatis` commands.


Patch:
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index aafca0c5..7c0ca13f 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -1958,27 +1958,61 @@ func (t *gdbThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
 	if !ok && t.p.bi.Arch.Name == "arm64" && regName == "x30" {
 		gdbreg, ok = t.regs.regs["lr"]
 	}
+	if !ok && regName == "rflags" {
+		// rr has eflags instead of rflags
+		regName = "eflags"
+		gdbreg, ok = t.regs.regs[regName]
+		if ok {
+			reg.FillBytes()
+			reg.Bytes = reg.Bytes[:4]
+		}
+	}
 	if !ok {
 		return fmt.Errorf("could not set register %s: not found", regName)
 	}
 	reg.FillBytes()
-	if len(reg.Bytes) != len(gdbreg.value) {
-		return fmt.Errorf("could not set register %s: wrong size, expected %d got %d", regName, len(gdbreg.value), len(reg.Bytes))
-	}
-	copy(gdbreg.value, reg.Bytes)
-	err := t.p.conn.writeRegister(t.strID, gdbreg.regnum, gdbreg.value)
-	if err != nil {
-		return err
+
+	wrongSizeErr := func(n int) error {
+		return fmt.Errorf("could not set register %s: wrong size, expected %d got %d", regName, n, len(reg.Bytes))
 	}
-	if t.p.conn.workaroundReg != nil && len(gdbreg.value) > 16 {
-		// This is a workaround for a bug in debugserver where register writes (P
-		// packet) on AVX-2 and AVX-512 registers are ignored unless they are
-		// followed by a write to an AVX register.
-		// See:
-		//  Issue #2767
-		//  https://bugs.llvm.org/show_bug.cgi?id=52362
-		reg := t.regs.gdbRegisterNew(t.p.conn.workaroundReg)
-		return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
+
+	if len(reg.Bytes) == len(gdbreg.value) {
+		copy(gdbreg.value, reg.Bytes)
+		err := t.p.conn.writeRegister(t.strID, gdbreg.regnum, gdbreg.value)
+		if err != nil {
+			return err
+		}
+		if t.p.conn.workaroundReg != nil && len(gdbreg.value) > 16 {
+			// This is a workaround for a bug in debugserver where register writes (P
+			// packet) on AVX-2 and AVX-512 registers are ignored unless they are
+			// followed by a write to an AVX register.
+			// See:
+			//  Issue #2767
+			//  https://bugs.llvm.org/show_bug.cgi?id=52362
+			reg := t.regs.gdbRegisterNew(t.p.conn.workaroundReg)
+			return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
+		}
+	} else if len(reg.Bytes) == 2*len(gdbreg.value) && strings.HasPrefix(regName, "xmm") {
+		// rr uses xmmN for the low part of the register and ymmNh for the high part
+		gdbregh, ok := t.regs.regs["y"+regName[1:]+"h"]
+		if !ok {
+			return wrongSizeErr(len(gdbreg.value))
+		}
+		if len(reg.Bytes) != len(gdbreg.value)+len(gdbregh.value) {
+			return wrongSizeErr(len(gdbreg.value) + len(gdbregh.value))
+		}
+		copy(gdbreg.value, reg.Bytes[:len(gdbreg.value)])
+		copy(gdbregh.value, reg.Bytes[len(gdbreg.value):])
+		err := t.p.conn.writeRegister(t.strID, gdbreg.regnum, gdbreg.value)
+		if err != nil {
+			return err
+		}
+		err = t.p.conn.writeRegister(t.strID, gdbregh.regnum, gdbregh.value)
+		if err != nil {
+			return err
+		}
+	} else {
+		return wrongSizeErr(len(gdbreg.value))
 	}
 	return nil
 }
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index 3c95d75e..ac5930b6 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -413,6 +413,13 @@ func (t *Target) createFatalThrowBreakpoint() {
 			bp.Name = FatalThrow
 		}
 	}
+	fatalpcs, err = FindFunctionLocation(t.Process, "runtime.fatal", 0)
+	if err == nil {
+		bp, err := t.SetBreakpoint(fatalThrowID, fatalpcs[0], UserBreakpoint, nil)
+		if err == nil {
+			bp.Name = FatalThrow
+		}
+	}
 }
 
 // CurrentThread returns the currently selected thread which will be used
diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go
index 27edf8b5..ee73cfeb 100644
--- a/pkg/terminal/terminal.go
+++ b/pkg/terminal/terminal.go
@@ -229,6 +229,8 @@ func (t *Term) Run() (int, error) {
 		}
 	}
 
+	var locs *trie.Trie
+
 	t.line.SetCompleter(func(line string) (c []string) {
 		cmd := t.cmds.Find(strings.Split(line, " ")[0], noPrefix)
 		switch cmd.aliases[0] {
@@ -243,6 +245,30 @@ func (t *Term) Run() (int, error) {
 		case "nullcmd", "nocmd":
 			commands := cmds.FuzzySearch(strings.ToLower(line))
 			c = append(c, commands...)
+		case "print", "whatis":
+			if locs == nil {
+				localVars, err := t.client.ListLocalVariables(
+					api.EvalScope{GoroutineID: -1, Frame: t.cmds.frame, DeferredCall: 0},
+					api.LoadConfig{},
+				)
+				if err != nil {
+					fmt.Fprintf(os.Stderr, "Unable to get local variables: %s\n", err)
+					break
+				}
+
+				locs = trie.New()
+				for _, loc := range localVars {
+					locs.Add(loc.Name, nil)
+				}
+			}
+
+			if spc := strings.LastIndex(line, " "); spc > 0 {
+				prefix := line[:spc] + " "
+				locals := locs.FuzzySearch(line[spc+1:])
+				for _, l := range locals {
+					c = append(c, prefix+l)
+				}
+			}
 		}
 		return
 	})
@@ -279,6 +305,8 @@ func (t *Term) Run() (int, error) {
 	_, _ = t.client.GetState()
 
 	for {
+		locs = nil
+
 		cmdstr, err := t.promptForInput()
 		if err != nil {
 			if err == io.EOF {
@@ -460,7 +488,7 @@ func (t *Term) handleExit() (int, error) {
 	return 0, nil
 }
 
-// loadConfig returns an api.LoadConfig with the parameterss specified in
+// loadConfig returns an api.LoadConfig with the parameters specified in
 // the configuration file.
 func (t *Term) loadConfig() api.LoadConfig {
 	r := api.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
diff --git a/service/dap/server.go b/service/dap/server.go
index 37dd9671..0045dff0 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -393,7 +393,7 @@ func (s *Session) Close() {
 	defer s.mu.Unlock()
 
 	if s.debugger != nil {
-		killProcess := s.config.Debugger.AttachPid == 0
+		killProcess := s.debugger.AttachPid() == 0
 		s.stopDebugSession(killProcess)
 	} else if s.noDebugProcess != nil {
 		s.stopNoDebugProcess()
@@ -1165,7 +1165,7 @@ func (s *Session) onDisconnectRequest(request *dap.DisconnectRequest) {
 		// In case of attach, we leave the program
 		// running by default, which can be
 		// overridden by an explicit request to terminate.
-		killProcess := s.config.Debugger.AttachPid == 0 || request.Arguments.TerminateDebuggee
+		killProcess := s.debugger.AttachPid() == 0 || request.Arguments.TerminateDebuggee
 		err = s.stopDebugSession(killProcess)
 	} else if s.noDebugProcess != nil {
 		s.stopNoDebugProcess()
@@ -1226,7 +1226,7 @@ func (s *Session) stopDebugSession(killProcess bool) error {
 	} else if killProcess {
 		s.logToConsole("Detaching and terminating target process")
 	} else {
-		s.logToConsole("Detaching without terminating target processs")
+		s.logToConsole("Detaching without terminating target process")
 	}
 	err = s.debugger.Detach(killProcess)
 	if err != nil {
@@ -1770,9 +1770,15 @@ func (s *Session) onAttachRequest(request *dap.AttachRequest) {
 		if s.config.Debugger.Backend == "rr" {
 			s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportsStepBack: true}}})
 		}
-		// Give the user an option to terminate this server when client disconnects (default is to leave it)
-		s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}})
-		// TODO(polina); also use SupportSuspendDebuggee when available
+		// Customize termination options for debugger and debuggee
+		if s.config.AcceptMulti {
+			// User can stop debugger with process or leave it running
+			s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}})
+			// TODO(polina): support SupportSuspendDebuggee when available
+		} else if s.config.Debugger.AttachPid > 0 {
+			// User can stop debugger with process or leave the processs running
+			s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}})
+		} // else program was launched and the only option will be to stop both
 	default:
 		s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach",
 			fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", args.Mode))
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 33649797..52c72fee 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -2205,6 +2205,10 @@ func (d *Debugger) BuildID() string {
 	return d.target.BinInfo().BuildID
 }
 
+func (d *Debugger) AttachPid() int {
+	return d.config.AttachPid
+}
+
 func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult {
 	traces := d.target.GetBufferedTracepoints()
 	if traces == nil {


Test patch:
diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go
index b8b43517..6f337d8f 100644
--- a/cmd/dlv/dlv_test.go
+++ b/cmd/dlv/dlv_test.go
@@ -754,10 +754,12 @@ func TestDAPCmdWithNoDebugBinary(t *testing.T) {
 	cmd.Wait()
 }
 
-func newDAPRemoteClient(t *testing.T, addr string) *daptest.Client {
+func newDAPRemoteClient(t *testing.T, addr string, isDlvAttach bool, isMulti bool) *daptest.Client {
 	c := daptest.NewClient(addr)
 	c.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
-	c.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
+	if isDlvAttach || isMulti {
+		c.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
+	}
 	c.ExpectInitializedEvent(t)
 	c.ExpectAttachResponse(t)
 	c.ConfigurationDoneRequest()
@@ -794,7 +796,7 @@ func TestRemoteDAPClient(t *testing.T) {
 		}
 	}()
 
-	client := newDAPRemoteClient(t, listenAddr)
+	client := newDAPRemoteClient(t, listenAddr, false, false)
 	client.ContinueRequest(1)
 	client.ExpectContinueResponse(t)
 	client.ExpectTerminatedEvent(t)
@@ -854,7 +856,7 @@ func TestRemoteDAPClientMulti(t *testing.T) {
 	dapclient0.ExpectErrorResponse(t)
 
 	// Client 1 connects and continues to main.main
-	dapclient := newDAPRemoteClient(t, listenAddr)
+	dapclient := newDAPRemoteClient(t, listenAddr, false, true)
 	dapclient.SetFunctionBreakpointsRequest([]godap.FunctionBreakpoint{{Name: "main.main"}})
 	dapclient.ExpectSetFunctionBreakpointsResponse(t)
 	dapclient.ContinueRequest(1)
@@ -864,7 +866,7 @@ func TestRemoteDAPClientMulti(t *testing.T) {
 	closeDAPRemoteMultiClient(t, dapclient, "halted")
 
 	// Client 2 reconnects at main.main and continues to process exit
-	dapclient2 := newDAPRemoteClient(t, listenAddr)
+	dapclient2 := newDAPRemoteClient(t, listenAddr, false, true)
 	dapclient2.CheckStopLocation(t, 1, "main.main", 5)
 	dapclient2.ContinueRequest(1)
 	dapclient2.ExpectContinueResponse(t)
@@ -924,7 +926,7 @@ func TestRemoteDAPClientAfterContinue(t *testing.T) {
 		}
 	}()
 
-	c := newDAPRemoteClient(t, listenAddr)
+	c := newDAPRemoteClient(t, listenAddr, false, true)
 	c.ContinueRequest(1)
 	c.ExpectContinueResponse(t)
 	c.DisconnectRequest()
@@ -933,7 +935,7 @@ func TestRemoteDAPClientAfterContinue(t *testing.T) {
 	c.ExpectTerminatedEvent(t)
 	c.Close()
 
-	c = newDAPRemoteClient(t, listenAddr)
+	c = newDAPRemoteClient(t, listenAddr, false, true)
 	c.DisconnectRequestWithKillOption(true)
 	c.ExpectOutputEventDetachingKill(t)
 	c.ExpectDisconnectResponse(t)
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 0a74ac2a..f156a6f1 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -5860,6 +5860,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
 	// time, after stepping out of debugCallV2.
 	// Fixes issue https://github.com/go-delve/delve/issues/2985
 	skipUnlessOn(t, "not relevant", "amd64")
+	protest.MustSupportFunctionCalls(t, testBackend)
 
 	withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) {
 		mainfn := p.BinInfo().LookupFunc["main.main"]
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 63c1404c..aeb169a3 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -77,6 +77,9 @@ func startDAPServerWithClient(t *testing.T, serverStopped chan struct{}) *daptes
 	return client
 }
 
+// Starts an empty server and a stripped down config just to establish a client connection.
+// To mock a server created by dap.NewServer(config) or serving dap.NewSession(conn, config, debugger)
+// set those arg fields manually after the server creation.
 func startDAPServer(t *testing.T, serverStopped chan struct{}) (server *Server, forceStop chan struct{}) {
 	// Start the DAP server.
 	listener, err := net.Listen("tcp", ":0")
@@ -6516,6 +6519,18 @@ func launchDebuggerWithTargetHalted(t *testing.T, fixture string) (*protest.Fixt
 	return &fixbin, dbg
 }
 
+func attachDebuggerWithTargetHalted(t *testing.T, fixture string) (*exec.Cmd, *debugger.Debugger) {
+	t.Helper()
+	fixbin := protest.BuildFixture(fixture, protest.AllNonOptimized)
+	cmd := execFixture(t, fixbin)
+	cfg := service.Config{Debugger: debugger.Config{Backend: "default", AttachPid: cmd.Process.Pid}}
+	dbg, err := debugger.New(&cfg.Debugger, nil) // debugger halts process on entry
+	if err != nil {
+		t.Fatal("failed to start debugger:", err)
+	}
+	return cmd, dbg
+}
+
 // runTestWithDebugger starts the server and sets its debugger, initializes a debug session,
 // runs test, then disconnects. Expects no running async handler at the end of test() (either
 // process is halted or debug session never launched.)
@@ -6528,6 +6543,10 @@ func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *dapt
 	if server.session == nil {
 		t.Fatal("DAP session is not ready")
 	}
+	// Mock dap.NewSession arguments, so
+	// this dap.Server can be used as a proxy for
+	// rpccommon.Server running dap.Session.
+	server.session.config.Debugger.AttachPid = dbg.AttachPid()
 	server.session.debugger = dbg
 	server.sessionMu.Unlock()
 	defer client.Close()
@@ -6537,7 +6556,7 @@ func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *dapt
 	test(client)
 
 	client.DisconnectRequest()
-	if server.config.Debugger.AttachPid == 0 { // launched target
+	if dbg.AttachPid() == 0 { // launched target
 		client.ExpectOutputEventDetachingKill(t)
 	} else { // attached to target
 		client.ExpectOutputEventDetachingNoKill(t)
@@ -6548,9 +6567,24 @@ func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *dapt
 	<-serverStopped
 }
 
-func TestAttachRemoteToHaltedTargetStopOnEntry(t *testing.T) {
+func TestAttachRemoteToDlvLaunchHaltedStopOnEntry(t *testing.T) {
 	// Halted + stop on entry
 	_, dbg := launchDebuggerWithTargetHalted(t, "increment")
+	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
+		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
+		client.ExpectInitializedEvent(t)
+		client.ExpectAttachResponse(t)
+		client.ConfigurationDoneRequest()
+		client.ExpectStoppedEvent(t)
+		client.ExpectConfigurationDoneResponse(t)
+	})
+}
+
+func TestAttachRemoteToDlvAttachHaltedStopOnEntry(t *testing.T) {
+	if runtime.GOOS == "freebsd" || runtime.GOOS == "windows" {
+		t.SkipNow()
+	}
+	cmd, dbg := attachDebuggerWithTargetHalted(t, "http_server")
 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
@@ -6560,6 +6594,7 @@ func TestAttachRemoteToHaltedTargetStopOnEntry(t *testing.T) {
 		client.ExpectStoppedEvent(t)
 		client.ExpectConfigurationDoneResponse(t)
 	})
+	cmd.Process.Kill()
 }
 
 func TestAttachRemoteToHaltedTargetContinueOnEntry(t *testing.T) {
@@ -6567,7 +6602,6 @@ func TestAttachRemoteToHaltedTargetContinueOnEntry(t *testing.T) {
 	_, dbg := launchDebuggerWithTargetHalted(t, "http_server")
 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false})
-		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 		client.ExpectInitializedEvent(t)
 		client.ExpectAttachResponse(t)
 		client.ConfigurationDoneRequest()
@@ -6584,7 +6618,6 @@ func TestAttachRemoteToRunningTargetStopOnEntry(t *testing.T) {
 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog")
 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
-		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 		client.ExpectInitializedEvent(t)
 		client.ExpectAttachResponse(t)
 		// Target is halted here
@@ -6604,7 +6637,6 @@ func TestAttachRemoteToRunningTargetContinueOnEntry(t *testing.T) {
 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog")
 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false})
-		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 		client.ExpectInitializedEvent(t)
 		client.ExpectAttachResponse(t)
 		// Target is halted here
@@ -6643,8 +6675,8 @@ func TestAttachRemoteMultiClientDisconnect(t *testing.T) {
 			if server.session == nil {
 				t.Fatal("dap session is not ready")
 			}
-			// DAP server doesn't support accept-multiclient, but we can use this
-			// hack to test the inner connection logic that can be used by a server that does.
+			// A dap.Server doesn't support accept-multiclient, but we can use this
+			// hack to test the inner connection logic that is used by a server that does.
 			server.session.config.AcceptMulti = true
 			_, server.session.debugger = launchDebuggerWithTargetHalted(t, "increment")
 			server.sessionMu.Unlock()
@@ -6672,7 +6704,7 @@ func TestAttachRemoteMultiClientDisconnect(t *testing.T) {
 			if tc.expect == closingClientSessionOnly {
 				// At this point a multi-client server is still running.
 				verifySessionStopped(t, server.session)
-				// Since it is a dap server, it cannot accept another client, so the only
+				// Since it is a dap.Server, it cannot accept another client, so the only
 				// way to take down the server is to force-kill it.
 				close(forceStop)
 			}
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index c8c4a7d0..6a83c397 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -1340,11 +1340,6 @@ func TestCallFunction(t *testing.T) {
 
 		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
 			for _, tc := range testcases117 {
-				if strings.Contains(tc.expr, "floatsum") && testBackend == "rr" {
-					// Can not set floating point registers with RR.
-					// See: https://github.com/rr-debugger/rr/issues/3208
-					continue
-				}
 				testCallFunction(t, p, tc)
 			}
 		}