]>
Commit | Line | Data |
---|---|---|
1d506c26 | 1 | # Copyright 2022-2024 Free Software Foundation, Inc. |
de7d7cb5 TT |
2 | |
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. | |
7 | # | |
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. | |
12 | # | |
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/>. | |
15 | ||
de7d7cb5 TT |
16 | import gdb |
17 | ||
18 | from .server import send_event | |
68caad9d | 19 | from .startup import exec_and_log, in_gdb_thread, log |
8a35f6b3 | 20 | from .modules import is_module, make_module |
de7d7cb5 TT |
21 | |
22 | ||
cfd00e80 TT |
23 | # True when the inferior is thought to be running, False otherwise. |
24 | # This may be accessed from any thread, which can be racy. However, | |
25 | # this unimportant because this global is only used for the | |
26 | # 'notStopped' response, which itself is inherently racy. | |
27 | inferior_running = False | |
28 | ||
29 | ||
de7d7cb5 TT |
30 | @in_gdb_thread |
31 | def _on_exit(event): | |
cfd00e80 TT |
32 | global inferior_running |
33 | inferior_running = False | |
de7d7cb5 TT |
34 | code = 0 |
35 | if hasattr(event, "exit_code"): | |
36 | code = event.exit_code | |
37 | send_event( | |
38 | "exited", | |
39 | { | |
40 | "exitCode": code, | |
41 | }, | |
42 | ) | |
21db866d | 43 | send_event("terminated") |
de7d7cb5 TT |
44 | |
45 | ||
14e461be TT |
46 | # When None, a "process" event has already been sent. When a string, |
47 | # it is the "startMethod" for that event. | |
48 | _process_event_kind = None | |
49 | ||
50 | ||
51 | @in_gdb_thread | |
52 | def send_process_event_once(): | |
53 | global _process_event_kind | |
54 | if _process_event_kind is not None: | |
55 | inf = gdb.selected_inferior() | |
56 | is_local = inf.connection.type == "native" | |
57 | data = { | |
58 | "isLocalProcess": is_local, | |
59 | "startMethod": _process_event_kind, | |
60 | # Could emit 'pointerSize' here too if we cared to. | |
61 | } | |
62 | if inf.progspace.filename: | |
63 | data["name"] = inf.progspace.filename | |
64 | if is_local: | |
65 | data["systemProcessId"] = inf.pid | |
66 | send_event("process", data) | |
67 | _process_event_kind = None | |
68 | ||
69 | ||
70 | @in_gdb_thread | |
71 | def expect_process(reason): | |
72 | """Indicate that DAP is starting or attaching to a process. | |
73 | ||
74 | REASON is the "startMethod" to include in the "process" event. | |
75 | """ | |
76 | global _process_event_kind | |
77 | _process_event_kind = reason | |
78 | ||
79 | ||
de7d7cb5 | 80 | @in_gdb_thread |
a18b53a8 | 81 | def thread_event(event, reason): |
14e461be | 82 | send_process_event_once() |
de7d7cb5 | 83 | send_event( |
a18b53a8 | 84 | "thread", |
de7d7cb5 | 85 | { |
a18b53a8 SF |
86 | "reason": reason, |
87 | "threadId": event.inferior_thread.global_num, | |
de7d7cb5 TT |
88 | }, |
89 | ) | |
90 | ||
91 | ||
92 | @in_gdb_thread | |
93 | def _new_thread(event): | |
cfd00e80 TT |
94 | global inferior_running |
95 | inferior_running = True | |
a18b53a8 SF |
96 | thread_event(event, "started") |
97 | ||
98 | ||
99 | @in_gdb_thread | |
100 | def _thread_exited(event): | |
101 | thread_event(event, "exited") | |
102 | ||
103 | ||
8a35f6b3 TT |
104 | @in_gdb_thread |
105 | def _new_objfile(event): | |
106 | if is_module(event.new_objfile): | |
107 | send_event( | |
108 | "module", | |
109 | { | |
110 | "reason": "new", | |
111 | "module": make_module(event.new_objfile), | |
112 | }, | |
113 | ) | |
114 | ||
115 | ||
100dbc6d TT |
116 | @in_gdb_thread |
117 | def _objfile_removed(event): | |
14e461be | 118 | send_process_event_once() |
100dbc6d TT |
119 | if is_module(event.objfile): |
120 | send_event( | |
121 | "module", | |
122 | { | |
123 | "reason": "removed", | |
124 | "module": make_module(event.objfile), | |
125 | }, | |
126 | ) | |
127 | ||
128 | ||
de7d7cb5 TT |
129 | _suppress_cont = False |
130 | ||
131 | ||
132 | @in_gdb_thread | |
133 | def _cont(event): | |
cfd00e80 TT |
134 | global inferior_running |
135 | inferior_running = True | |
de7d7cb5 TT |
136 | global _suppress_cont |
137 | if _suppress_cont: | |
138 | log("_suppress_cont case") | |
139 | _suppress_cont = False | |
140 | else: | |
141 | send_event( | |
142 | "continued", | |
143 | { | |
144 | "threadId": gdb.selected_thread().global_num, | |
145 | "allThreadsContinued": True, | |
146 | }, | |
147 | ) | |
148 | ||
149 | ||
0b32d225 TT |
150 | _suppress_stop = False |
151 | ||
152 | ||
153 | @in_gdb_thread | |
154 | def suppress_stop(): | |
155 | """Indicate that the next stop should not emit an event.""" | |
156 | global _suppress_stop | |
157 | _suppress_stop = True | |
158 | ||
159 | ||
7729e7c0 | 160 | _expected_pause = False |
de7d7cb5 TT |
161 | |
162 | ||
163 | @in_gdb_thread | |
7729e7c0 TT |
164 | def exec_and_expect_stop(cmd, expected_pause=False): |
165 | """A wrapper for exec_and_log that sets the continue-suppression flag. | |
166 | ||
167 | When EXPECTED_PAUSE is True, a stop that looks like a pause (e.g., | |
168 | a SIGINT) will be reported as "pause" instead. | |
169 | """ | |
170 | global _expected_pause | |
171 | _expected_pause = expected_pause | |
172 | global _suppress_cont | |
173 | # If we're expecting a pause, then we're definitely not | |
174 | # continuing. | |
175 | _suppress_cont = not expected_pause | |
68caad9d TT |
176 | # FIXME if the call fails should we clear _suppress_cont? |
177 | exec_and_log(cmd) | |
de7d7cb5 TT |
178 | |
179 | ||
7729e7c0 TT |
180 | # Map from gdb stop reasons to DAP stop reasons. Some of these can't |
181 | # be seen ordinarily in DAP -- only if the client lets the user toggle | |
182 | # some settings (e.g. stop-on-solib-events) or enter commands (e.g., | |
183 | # 'until'). | |
184 | stop_reason_map = { | |
185 | "breakpoint-hit": "breakpoint", | |
186 | "watchpoint-trigger": "data breakpoint", | |
187 | "read-watchpoint-trigger": "data breakpoint", | |
188 | "access-watchpoint-trigger": "data breakpoint", | |
189 | "function-finished": "step", | |
190 | "location-reached": "step", | |
191 | "watchpoint-scope": "data breakpoint", | |
192 | "end-stepping-range": "step", | |
193 | "exited-signalled": "exited", | |
194 | "exited": "exited", | |
195 | "exited-normally": "exited", | |
196 | "signal-received": "signal", | |
197 | "solib-event": "solib", | |
198 | "fork": "fork", | |
199 | "vfork": "vfork", | |
200 | "syscall-entry": "syscall-entry", | |
201 | "syscall-return": "syscall-return", | |
202 | "exec": "exec", | |
203 | "no-history": "no-history", | |
204 | } | |
205 | ||
206 | ||
de7d7cb5 TT |
207 | @in_gdb_thread |
208 | def _on_stop(event): | |
cfd00e80 TT |
209 | global inferior_running |
210 | inferior_running = False | |
0b32d225 TT |
211 | |
212 | global _suppress_stop | |
213 | if _suppress_stop: | |
214 | _suppress_stop = False | |
215 | log("suppressing stop in _on_stop") | |
216 | return | |
217 | ||
de7d7cb5 | 218 | log("entering _on_stop: " + repr(event)) |
7729e7c0 | 219 | log(" details: " + repr(event.details)) |
de7d7cb5 TT |
220 | obj = { |
221 | "threadId": gdb.selected_thread().global_num, | |
de7d7cb5 TT |
222 | "allThreadsStopped": True, |
223 | } | |
224 | if isinstance(event, gdb.BreakpointEvent): | |
de7d7cb5 | 225 | obj["hitBreakpointIds"] = [x.number for x in event.breakpoints] |
7729e7c0 | 226 | global _expected_pause |
0b32d225 TT |
227 | # Some stop events still do not emit details. For example, |
228 | # 'attach' causes a reason-less stop. | |
229 | if "reason" not in event.details: | |
230 | # This can only really happen via a "repl" evaluation of | |
231 | # something like "attach". In this case just emit a generic | |
232 | # stop. | |
233 | obj["reason"] = "stopped" | |
234 | elif ( | |
7729e7c0 | 235 | _expected_pause |
0b32d225 | 236 | and event.details["reason"] == "signal-received" |
7729e7c0 TT |
237 | and event.details["signal-name"] in ("SIGINT", "SIGSTOP") |
238 | ): | |
239 | obj["reason"] = "pause" | |
240 | else: | |
0b32d225 TT |
241 | global stop_reason_map |
242 | obj["reason"] = stop_reason_map[event.details["reason"]] | |
7729e7c0 | 243 | _expected_pause = False |
de7d7cb5 TT |
244 | send_event("stopped", obj) |
245 | ||
246 | ||
c618a1c5 TT |
247 | # This keeps a bit of state between the start of an inferior call and |
248 | # the end. If the inferior was already running when the call started | |
249 | # (as can happen if a breakpoint condition calls a function), then we | |
250 | # do not want to emit 'continued' or 'stop' events for the call. Note | |
251 | # that, for some reason, gdb.events.cont does not fire for an infcall. | |
252 | _infcall_was_running = False | |
253 | ||
254 | ||
255 | @in_gdb_thread | |
256 | def _on_inferior_call(event): | |
257 | global _infcall_was_running | |
7729e7c0 | 258 | global inferior_running |
c618a1c5 TT |
259 | if isinstance(event, gdb.InferiorCallPreEvent): |
260 | _infcall_was_running = inferior_running | |
261 | if not _infcall_was_running: | |
262 | _cont(None) | |
263 | else: | |
7729e7c0 TT |
264 | # If the inferior is already marked as stopped here, then that |
265 | # means that the call caused some other stop, and we don't | |
266 | # want to double-report it. | |
267 | if not _infcall_was_running and inferior_running: | |
268 | inferior_running = False | |
269 | obj = { | |
270 | "threadId": gdb.selected_thread().global_num, | |
271 | "allThreadsStopped": True, | |
272 | # DAP says any string is ok. | |
273 | "reason": "function call", | |
274 | } | |
275 | global _expected_pause | |
276 | _expected_pause = False | |
277 | send_event("stopped", obj) | |
c618a1c5 TT |
278 | |
279 | ||
de7d7cb5 TT |
280 | gdb.events.stop.connect(_on_stop) |
281 | gdb.events.exited.connect(_on_exit) | |
de7d7cb5 | 282 | gdb.events.new_thread.connect(_new_thread) |
a18b53a8 | 283 | gdb.events.thread_exited.connect(_thread_exited) |
de7d7cb5 | 284 | gdb.events.cont.connect(_cont) |
8a35f6b3 | 285 | gdb.events.new_objfile.connect(_new_objfile) |
100dbc6d | 286 | gdb.events.free_objfile.connect(_objfile_removed) |
c618a1c5 | 287 | gdb.events.inferior_call.connect(_on_inferior_call) |