mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 02:36:18 +08:00 
			
		
		
		
	dap: remote attach must not use terminateDebuggee in single-client launch mode (#2995)
This commit is contained in:
		| @ -754,10 +754,12 @@ func TestDAPCmdWithNoDebugBinary(t *testing.T) { | |||||||
| 	cmd.Wait() | 	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 := daptest.NewClient(addr) | ||||||
| 	c.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) | 	c.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) | ||||||
|  | 	if isDlvAttach || isMulti { | ||||||
| 		c.ExpectCapabilitiesEventSupportTerminateDebuggee(t) | 		c.ExpectCapabilitiesEventSupportTerminateDebuggee(t) | ||||||
|  | 	} | ||||||
| 	c.ExpectInitializedEvent(t) | 	c.ExpectInitializedEvent(t) | ||||||
| 	c.ExpectAttachResponse(t) | 	c.ExpectAttachResponse(t) | ||||||
| 	c.ConfigurationDoneRequest() | 	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.ContinueRequest(1) | ||||||
| 	client.ExpectContinueResponse(t) | 	client.ExpectContinueResponse(t) | ||||||
| 	client.ExpectTerminatedEvent(t) | 	client.ExpectTerminatedEvent(t) | ||||||
| @ -854,7 +856,7 @@ func TestRemoteDAPClientMulti(t *testing.T) { | |||||||
| 	dapclient0.ExpectErrorResponse(t) | 	dapclient0.ExpectErrorResponse(t) | ||||||
|  |  | ||||||
| 	// Client 1 connects and continues to main.main | 	// 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.SetFunctionBreakpointsRequest([]godap.FunctionBreakpoint{{Name: "main.main"}}) | ||||||
| 	dapclient.ExpectSetFunctionBreakpointsResponse(t) | 	dapclient.ExpectSetFunctionBreakpointsResponse(t) | ||||||
| 	dapclient.ContinueRequest(1) | 	dapclient.ContinueRequest(1) | ||||||
| @ -864,7 +866,7 @@ func TestRemoteDAPClientMulti(t *testing.T) { | |||||||
| 	closeDAPRemoteMultiClient(t, dapclient, "halted") | 	closeDAPRemoteMultiClient(t, dapclient, "halted") | ||||||
|  |  | ||||||
| 	// Client 2 reconnects at main.main and continues to process exit | 	// 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.CheckStopLocation(t, 1, "main.main", 5) | ||||||
| 	dapclient2.ContinueRequest(1) | 	dapclient2.ContinueRequest(1) | ||||||
| 	dapclient2.ExpectContinueResponse(t) | 	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.ContinueRequest(1) | ||||||
| 	c.ExpectContinueResponse(t) | 	c.ExpectContinueResponse(t) | ||||||
| 	c.DisconnectRequest() | 	c.DisconnectRequest() | ||||||
| @ -933,7 +935,7 @@ func TestRemoteDAPClientAfterContinue(t *testing.T) { | |||||||
| 	c.ExpectTerminatedEvent(t) | 	c.ExpectTerminatedEvent(t) | ||||||
| 	c.Close() | 	c.Close() | ||||||
|  |  | ||||||
| 	c = newDAPRemoteClient(t, listenAddr) | 	c = newDAPRemoteClient(t, listenAddr, false, true) | ||||||
| 	c.DisconnectRequestWithKillOption(true) | 	c.DisconnectRequestWithKillOption(true) | ||||||
| 	c.ExpectOutputEventDetachingKill(t) | 	c.ExpectOutputEventDetachingKill(t) | ||||||
| 	c.ExpectDisconnectResponse(t) | 	c.ExpectDisconnectResponse(t) | ||||||
|  | |||||||
| @ -393,7 +393,7 @@ func (s *Session) Close() { | |||||||
| 	defer s.mu.Unlock() | 	defer s.mu.Unlock() | ||||||
|  |  | ||||||
| 	if s.debugger != nil { | 	if s.debugger != nil { | ||||||
| 		killProcess := s.config.Debugger.AttachPid == 0 | 		killProcess := s.debugger.AttachPid() == 0 | ||||||
| 		s.stopDebugSession(killProcess) | 		s.stopDebugSession(killProcess) | ||||||
| 	} else if s.noDebugProcess != nil { | 	} else if s.noDebugProcess != nil { | ||||||
| 		s.stopNoDebugProcess() | 		s.stopNoDebugProcess() | ||||||
| @ -1165,7 +1165,7 @@ func (s *Session) onDisconnectRequest(request *dap.DisconnectRequest) { | |||||||
| 		// In case of attach, we leave the program | 		// In case of attach, we leave the program | ||||||
| 		// running by default, which can be | 		// running by default, which can be | ||||||
| 		// overridden by an explicit request to terminate. | 		// 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) | 		err = s.stopDebugSession(killProcess) | ||||||
| 	} else if s.noDebugProcess != nil { | 	} else if s.noDebugProcess != nil { | ||||||
| 		s.stopNoDebugProcess() | 		s.stopNoDebugProcess() | ||||||
| @ -1226,7 +1226,7 @@ func (s *Session) stopDebugSession(killProcess bool) error { | |||||||
| 	} else if killProcess { | 	} else if killProcess { | ||||||
| 		s.logToConsole("Detaching and terminating target process") | 		s.logToConsole("Detaching and terminating target process") | ||||||
| 	} else { | 	} else { | ||||||
| 		s.logToConsole("Detaching without terminating target processs") | 		s.logToConsole("Detaching without terminating target process") | ||||||
| 	} | 	} | ||||||
| 	err = s.debugger.Detach(killProcess) | 	err = s.debugger.Detach(killProcess) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -1770,9 +1770,15 @@ func (s *Session) onAttachRequest(request *dap.AttachRequest) { | |||||||
| 		if s.config.Debugger.Backend == "rr" { | 		if s.config.Debugger.Backend == "rr" { | ||||||
| 			s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportsStepBack: true}}}) | 			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) | 		// 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}}}) | 			s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}}) | ||||||
| 		// TODO(polina); also use SupportSuspendDebuggee when available | 			// 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: | 	default: | ||||||
| 		s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", | 		s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", | ||||||
| 			fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", args.Mode)) | 			fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", args.Mode)) | ||||||
|  | |||||||
| @ -77,6 +77,9 @@ func startDAPServerWithClient(t *testing.T, serverStopped chan struct{}) *daptes | |||||||
| 	return client | 	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{}) { | func startDAPServer(t *testing.T, serverStopped chan struct{}) (server *Server, forceStop chan struct{}) { | ||||||
| 	// Start the DAP server. | 	// Start the DAP server. | ||||||
| 	listener, err := net.Listen("tcp", ":0") | 	listener, err := net.Listen("tcp", ":0") | ||||||
| @ -6516,6 +6519,18 @@ func launchDebuggerWithTargetHalted(t *testing.T, fixture string) (*protest.Fixt | |||||||
| 	return &fixbin, dbg | 	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, | // 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 | // runs test, then disconnects. Expects no running async handler at the end of test() (either | ||||||
| // process is halted or debug session never launched.) | // 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 { | 	if server.session == nil { | ||||||
| 		t.Fatal("DAP session is not ready") | 		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.session.debugger = dbg | ||||||
| 	server.sessionMu.Unlock() | 	server.sessionMu.Unlock() | ||||||
| 	defer client.Close() | 	defer client.Close() | ||||||
| @ -6537,7 +6556,7 @@ func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *dapt | |||||||
| 	test(client) | 	test(client) | ||||||
|  |  | ||||||
| 	client.DisconnectRequest() | 	client.DisconnectRequest() | ||||||
| 	if server.config.Debugger.AttachPid == 0 { // launched target | 	if dbg.AttachPid() == 0 { // launched target | ||||||
| 		client.ExpectOutputEventDetachingKill(t) | 		client.ExpectOutputEventDetachingKill(t) | ||||||
| 	} else { // attached to target | 	} else { // attached to target | ||||||
| 		client.ExpectOutputEventDetachingNoKill(t) | 		client.ExpectOutputEventDetachingNoKill(t) | ||||||
| @ -6548,9 +6567,24 @@ func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *dapt | |||||||
| 	<-serverStopped | 	<-serverStopped | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestAttachRemoteToHaltedTargetStopOnEntry(t *testing.T) { | func TestAttachRemoteToDlvLaunchHaltedStopOnEntry(t *testing.T) { | ||||||
| 	// Halted + stop on entry | 	// Halted + stop on entry | ||||||
| 	_, dbg := launchDebuggerWithTargetHalted(t, "increment") | 	_, 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) { | 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | ||||||
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) | 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) | ||||||
| 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t) | 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t) | ||||||
| @ -6560,6 +6594,7 @@ func TestAttachRemoteToHaltedTargetStopOnEntry(t *testing.T) { | |||||||
| 		client.ExpectStoppedEvent(t) | 		client.ExpectStoppedEvent(t) | ||||||
| 		client.ExpectConfigurationDoneResponse(t) | 		client.ExpectConfigurationDoneResponse(t) | ||||||
| 	}) | 	}) | ||||||
|  | 	cmd.Process.Kill() | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestAttachRemoteToHaltedTargetContinueOnEntry(t *testing.T) { | func TestAttachRemoteToHaltedTargetContinueOnEntry(t *testing.T) { | ||||||
| @ -6567,7 +6602,6 @@ func TestAttachRemoteToHaltedTargetContinueOnEntry(t *testing.T) { | |||||||
| 	_, dbg := launchDebuggerWithTargetHalted(t, "http_server") | 	_, dbg := launchDebuggerWithTargetHalted(t, "http_server") | ||||||
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | ||||||
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false}) | 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false}) | ||||||
| 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t) |  | ||||||
| 		client.ExpectInitializedEvent(t) | 		client.ExpectInitializedEvent(t) | ||||||
| 		client.ExpectAttachResponse(t) | 		client.ExpectAttachResponse(t) | ||||||
| 		client.ConfigurationDoneRequest() | 		client.ConfigurationDoneRequest() | ||||||
| @ -6584,7 +6618,6 @@ func TestAttachRemoteToRunningTargetStopOnEntry(t *testing.T) { | |||||||
| 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog") | 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog") | ||||||
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | ||||||
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) | 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) | ||||||
| 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t) |  | ||||||
| 		client.ExpectInitializedEvent(t) | 		client.ExpectInitializedEvent(t) | ||||||
| 		client.ExpectAttachResponse(t) | 		client.ExpectAttachResponse(t) | ||||||
| 		// Target is halted here | 		// Target is halted here | ||||||
| @ -6604,7 +6637,6 @@ func TestAttachRemoteToRunningTargetContinueOnEntry(t *testing.T) { | |||||||
| 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog") | 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog") | ||||||
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | 	runTestWithDebugger(t, dbg, func(client *daptest.Client) { | ||||||
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false}) | 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false}) | ||||||
| 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t) |  | ||||||
| 		client.ExpectInitializedEvent(t) | 		client.ExpectInitializedEvent(t) | ||||||
| 		client.ExpectAttachResponse(t) | 		client.ExpectAttachResponse(t) | ||||||
| 		// Target is halted here | 		// Target is halted here | ||||||
| @ -6643,8 +6675,8 @@ func TestAttachRemoteMultiClientDisconnect(t *testing.T) { | |||||||
| 			if server.session == nil { | 			if server.session == nil { | ||||||
| 				t.Fatal("dap session is not ready") | 				t.Fatal("dap session is not ready") | ||||||
| 			} | 			} | ||||||
| 			// DAP server doesn't support accept-multiclient, but we can use this | 			// A 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. | 			// hack to test the inner connection logic that is used by a server that does. | ||||||
| 			server.session.config.AcceptMulti = true | 			server.session.config.AcceptMulti = true | ||||||
| 			_, server.session.debugger = launchDebuggerWithTargetHalted(t, "increment") | 			_, server.session.debugger = launchDebuggerWithTargetHalted(t, "increment") | ||||||
| 			server.sessionMu.Unlock() | 			server.sessionMu.Unlock() | ||||||
| @ -6672,7 +6704,7 @@ func TestAttachRemoteMultiClientDisconnect(t *testing.T) { | |||||||
| 			if tc.expect == closingClientSessionOnly { | 			if tc.expect == closingClientSessionOnly { | ||||||
| 				// At this point a multi-client server is still running. | 				// At this point a multi-client server is still running. | ||||||
| 				verifySessionStopped(t, server.session) | 				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. | 				// way to take down the server is to force-kill it. | ||||||
| 				close(forceStop) | 				close(forceStop) | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -2205,6 +2205,10 @@ func (d *Debugger) BuildID() string { | |||||||
| 	return d.target.BinInfo().BuildID | 	return d.target.BinInfo().BuildID | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d *Debugger) AttachPid() int { | ||||||
|  | 	return d.config.AttachPid | ||||||
|  | } | ||||||
|  |  | ||||||
| func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult { | func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult { | ||||||
| 	traces := d.target.GetBufferedTracepoints() | 	traces := d.target.GetBufferedTracepoints() | ||||||
| 	if traces == nil { | 	if traces == nil { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 polinasok
					polinasok