]>
git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - gdb/python/lib/gdb/dap/events.py
e8f26550a1611f3efacece89be99f166446270c5
1 # Copyright 2022-2025 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/>.
18 from .modules
import is_module
, make_module
19 from .scopes
import set_finish_value
20 from .server
import send_event
21 from .startup
import exec_and_log
, in_gdb_thread
, log
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 is unimportant because this global is only used for the
26 # 'notStopped' response, which itself is inherently racy.
27 inferior_running
= False
32 global inferior_running
33 inferior_running
= False
35 if hasattr(event
, "exit_code"):
36 code
= event
.exit_code
43 send_event("terminated")
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
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"
58 "isLocalProcess": is_local
,
59 "startMethod": _process_event_kind
,
60 # Could emit 'pointerSize' here too if we cared to.
62 if inf
.progspace
.filename
:
63 data
["name"] = inf
.progspace
.filename
65 data
["systemProcessId"] = inf
.pid
66 send_event("process", data
)
67 _process_event_kind
= None
71 def expect_process(reason
):
72 """Indicate that DAP is starting or attaching to a process.
74 REASON is the "startMethod" to include in the "process" event.
76 global _process_event_kind
77 _process_event_kind
= reason
81 def thread_event(event
, reason
):
82 send_process_event_once()
87 "threadId": event
.inferior_thread
.global_num
,
93 def _new_thread(event
):
94 global inferior_running
95 inferior_running
= True
96 thread_event(event
, "started")
100 def _thread_exited(event
):
101 thread_event(event
, "exited")
105 def _new_objfile(event
):
106 if is_module(event
.new_objfile
):
111 "module": make_module(event
.new_objfile
),
117 def _objfile_removed(event
):
118 send_process_event_once()
119 if is_module(event
.objfile
):
124 "module": make_module(event
.objfile
),
129 _suppress_cont
= False
134 global inferior_running
135 inferior_running
= True
136 global _suppress_cont
138 log("_suppress_cont case")
139 _suppress_cont
= False
144 "threadId": gdb
.selected_thread().global_num
,
145 "allThreadsContinued": True,
150 _expected_stop_reason
= None
154 def expect_stop(reason
: str):
155 """Indicate that the next stop should be for REASON."""
156 global _expected_stop_reason
157 _expected_stop_reason
= reason
160 _expected_pause
= False
164 def exec_and_expect_stop(cmd
, expected_pause
=False, propagate_exception
=False):
165 """A wrapper for exec_and_log that sets the continue-suppression flag.
167 When EXPECTED_PAUSE is True, a stop that looks like a pause (e.g.,
168 a SIGINT) will be reported as "pause" instead.
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
175 _suppress_cont
= not expected_pause
176 # FIXME if the call fails should we clear _suppress_cont?
177 exec_and_log(cmd
, propagate_exception
)
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.,
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",
195 "exited-normally": "exited",
196 "signal-received": "signal",
197 "solib-event": "solib",
200 "syscall-entry": "syscall-entry",
201 "syscall-return": "syscall-return",
203 "no-history": "no-history",
209 global inferior_running
210 inferior_running
= False
212 log("entering _on_stop: " + repr(event
))
213 if hasattr(event
, "details"):
214 log(" details: " + repr(event
.details
))
216 "threadId": gdb
.selected_thread().global_num
,
217 "allThreadsStopped": True,
219 if isinstance(event
, gdb
.BreakpointEvent
):
220 obj
["hitBreakpointIds"] = [x
.number
for x
in event
.breakpoints
]
221 if hasattr(event
, "details") and "finish-value" in event
.details
:
222 set_finish_value(event
.details
["finish-value"])
224 global _expected_pause
225 global _expected_stop_reason
226 if _expected_stop_reason
is not None:
227 obj
["reason"] = _expected_stop_reason
228 _expected_stop_reason
= None
229 elif "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
233 obj
["reason"] = "stopped"
236 and event
.details
["reason"] == "signal-received"
237 and event
.details
["signal-name"] in ("SIGINT", "SIGSTOP")
239 obj
["reason"] = "pause"
241 obj
["reason"] = stop_reason_map
[event
.details
["reason"]]
242 _expected_pause
= False
243 send_event("stopped", obj
)
246 # This keeps a bit of state between the start of an inferior call and
247 # the end. If the inferior was already running when the call started
248 # (as can happen if a breakpoint condition calls a function), then we
249 # do not want to emit 'continued' or 'stop' events for the call. Note
250 # that, for some reason, gdb.events.cont does not fire for an infcall.
251 _infcall_was_running
= False
255 def _on_inferior_call(event
):
256 global _infcall_was_running
257 global inferior_running
258 if isinstance(event
, gdb
.InferiorCallPreEvent
):
259 _infcall_was_running
= inferior_running
260 if not _infcall_was_running
:
263 # If the inferior is already marked as stopped here, then that
264 # means that the call caused some other stop, and we don't
265 # want to double-report it.
266 if not _infcall_was_running
and inferior_running
:
267 inferior_running
= False
269 "threadId": gdb
.selected_thread().global_num
,
270 "allThreadsStopped": True,
271 # DAP says any string is ok.
272 "reason": "function call",
274 global _expected_pause
275 _expected_pause
= False
276 send_event("stopped", obj
)
279 gdb
.events
.stop
.connect(_on_stop
)
280 gdb
.events
.exited
.connect(_on_exit
)
281 gdb
.events
.new_thread
.connect(_new_thread
)
282 gdb
.events
.thread_exited
.connect(_thread_exited
)
283 gdb
.events
.cont
.connect(_cont
)
284 gdb
.events
.new_objfile
.connect(_new_objfile
)
285 gdb
.events
.free_objfile
.connect(_objfile_removed
)
286 gdb
.events
.inferior_call
.connect(_on_inferior_call
)