1 # Copyright 2022-2023 Free Software Foundation, Inc.
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from .io
import start_json_writer
, read_json
21 from .startup
import (
26 send_gdb_with_response
,
28 from .typecheck
import type_check
31 # Map capability names to values.
34 # Map command names to callables.
42 """The DAP server class."""
44 def __init__(self
, in_stream
, out_stream
, child_stream
):
45 self
.in_stream
= in_stream
46 self
.out_stream
= out_stream
47 self
.child_stream
= child_stream
48 self
.delayed_events
= []
49 # This queue accepts JSON objects that are then sent to the
50 # DAP client. Writing is done in a separate thread to avoid
51 # blocking the read loop.
52 if sys
.version_info
[0] == 3 and sys
.version_info
[1] <= 6:
53 self
.write_queue
= queue
.Queue()
55 self
.write_queue
= queue
.SimpleQueue()
60 # Treat PARAMS as a JSON-RPC request and perform its action.
61 # PARAMS is just a dictionary from the JSON.
63 def _handle_command(self
, params
):
64 # We don't handle 'cancel' for now.
66 "request_seq": params
["seq"],
68 "command": params
["command"],
71 if "arguments" in params
:
72 args
= params
["arguments"]
76 body
= _commands
[params
["command"]](**args
)
79 result
["success"] = True
80 except BaseException
as e
:
82 result
["success"] = False
83 result
["message"] = str(e
)
86 # Read inferior output and sends OutputEvents to the client. It
87 # is run in its own thread.
88 def _read_inferior_output(self
):
90 line
= self
.child_stream
.readline()
99 # Send OBJ to the client, logging first if needed.
100 def _send_json(self
, obj
):
101 log("WROTE: <<<" + json
.dumps(obj
) + ">>>")
102 self
.write_queue
.put(obj
)
106 """The main loop of the DAP server."""
107 # Before looping, start the thread that writes JSON to the
108 # client, and the thread that reads output from the inferior.
109 start_thread("output reader", self
._read
_inferior
_output
)
110 start_json_writer(self
.out_stream
, self
.write_queue
)
112 cmd
= read_json(self
.in_stream
)
113 log("READ: <<<" + json
.dumps(cmd
) + ">>>")
114 result
= self
._handle
_command
(cmd
)
115 self
._send
_json
(result
)
116 events
= self
.delayed_events
117 self
.delayed_events
= []
118 for event
, body
in events
:
119 self
.send_event(event
, body
)
120 # Got the terminate request. This is handled by the
121 # JSON-writing thread, so that we can ensure that all
122 # responses are flushed to the client before exiting.
123 self
.write_queue
.put(None)
126 def send_event_later(self
, event
, body
=None):
127 """Send a DAP event back to the client, but only after the
128 current request has completed."""
129 self
.delayed_events
.append((event
, body
))
131 # Note that this does not need to be run in any particular thread,
132 # because it just creates an object and writes it to a thread-safe
134 def send_event(self
, event
, body
=None):
135 """Send an event to the DAP client.
136 EVENT is the name of the event, a string.
137 BODY is the body of the event, an arbitrary object."""
147 """Request that the server shut down."""
148 # Just set a flag. This operation is complicated because we
149 # want to write the result of the request before exiting. See
154 def send_event(event
, body
):
155 """Send an event to the DAP client.
156 EVENT is the name of the event, a string.
157 BODY is the body of the event, an arbitrary object."""
159 _server
.send_event(event
, body
)
163 """A decorator that indicates that the wrapper function implements
164 the DAP request NAME."""
168 # All requests must run in the DAP thread.
169 # Also type-check the calls.
170 func
= in_dap_thread(type_check(func
))
171 _commands
[name
] = func
177 def capability(name
, value
=True):
178 """A decorator that indicates that the wrapper function implements
179 the DAP capability NAME."""
183 _capabilities
[name
] = value
189 def client_bool_capability(name
):
190 """Return the value of a boolean client capability.
192 If the capability was not specified, or did not have boolean type,
193 False is returned."""
195 if name
in _server
.config
and isinstance(_server
.config
[name
], bool):
196 return _server
.config
[name
]
200 @request("initialize")
201 def initialize(**args
):
202 global _server
, _capabilities
203 _server
.config
= args
204 _server
.send_event_later("initialized")
205 return _capabilities
.copy()
208 @request("terminate")
209 @capability("supportsTerminateRequest")
210 def terminate(**args
):
211 # We can ignore the result here, because we only really need to
213 send_gdb_with_response("kill")
216 @request("disconnect")
217 @capability("supportTerminateDebuggee")
218 def disconnect(*, terminateDebuggee
: bool = False, **args
):
219 if terminateDebuggee
: