mirror of
https://github.com/go-delve/delve.git
synced 2025-10-30 02:07:58 +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})
|
||||||
c.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
|
if isDlvAttach || isMulti {
|
||||||
|
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
|
||||||
s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}})
|
if s.config.AcceptMulti {
|
||||||
// TODO(polina); also use SupportSuspendDebuggee when available
|
// 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:
|
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