]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blame - gdb/python/lib/gdb/dap/events.py
gas pending_bundle_size assert
[thirdparty/binutils-gdb.git] / gdb / python / lib / gdb / dap / events.py
CommitLineData
d01e8234 1# Copyright 2022-2025 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
16import gdb
17
c2cf30e7 18from .modules import is_module, make_module
2755241d 19from .scopes import set_finish_value
cbaa41b3 20from .server import send_event
68caad9d 21from .startup import exec_and_log, in_gdb_thread, log
de7d7cb5 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,
79f00963 25# this is unimportant because this global is only used for the
cfd00e80
TT
26# 'notStopped' response, which itself is inherently racy.
27inferior_running = False
28
29
de7d7cb5
TT
30@in_gdb_thread
31def _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
52def 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
71def 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 81def 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
93def _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
100def _thread_exited(event):
101 thread_event(event, "exited")
102
103
8a35f6b3
TT
104@in_gdb_thread
105def _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
117def _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
133def _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
0c566ea7 150_expected_stop_reason = None
0b32d225
TT
151
152
153@in_gdb_thread
0c566ea7
TT
154def expect_stop(reason: str):
155 """Indicate that the next stop should be for REASON."""
156 global _expected_stop_reason
157 _expected_stop_reason = reason
0b32d225
TT
158
159
7729e7c0 160_expected_pause = False
de7d7cb5
TT
161
162
163@in_gdb_thread
42dc1b7f 164def exec_and_expect_stop(cmd, expected_pause=False, propagate_exception=False):
7729e7c0
TT
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 176 # FIXME if the call fails should we clear _suppress_cont?
42dc1b7f 177 exec_and_log(cmd, propagate_exception)
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').
184stop_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
208def _on_stop(event):
cfd00e80
TT
209 global inferior_running
210 inferior_running = False
0b32d225 211
de7d7cb5 212 log("entering _on_stop: " + repr(event))
0c566ea7
TT
213 if hasattr(event, "details"):
214 log(" details: " + repr(event.details))
de7d7cb5
TT
215 obj = {
216 "threadId": gdb.selected_thread().global_num,
de7d7cb5
TT
217 "allThreadsStopped": True,
218 }
219 if isinstance(event, gdb.BreakpointEvent):
de7d7cb5 220 obj["hitBreakpointIds"] = [x.number for x in event.breakpoints]
2755241d
TT
221 if hasattr(event, "details") and "finish-value" in event.details:
222 set_finish_value(event.details["finish-value"])
0c566ea7 223
7729e7c0 224 global _expected_pause
0c566ea7
TT
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:
0b32d225
TT
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 241 obj["reason"] = stop_reason_map[event.details["reason"]]
7729e7c0 242 _expected_pause = False
cbaa41b3 243 send_event("stopped", obj)
de7d7cb5
TT
244
245
c618a1c5
TT
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
252
253
254@in_gdb_thread
255def _on_inferior_call(event):
256 global _infcall_was_running
7729e7c0 257 global inferior_running
c618a1c5
TT
258 if isinstance(event, gdb.InferiorCallPreEvent):
259 _infcall_was_running = inferior_running
260 if not _infcall_was_running:
261 _cont(None)
262 else:
7729e7c0
TT
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
268 obj = {
269 "threadId": gdb.selected_thread().global_num,
270 "allThreadsStopped": True,
271 # DAP says any string is ok.
272 "reason": "function call",
273 }
274 global _expected_pause
275 _expected_pause = False
276 send_event("stopped", obj)
c618a1c5
TT
277
278
de7d7cb5
TT
279gdb.events.stop.connect(_on_stop)
280gdb.events.exited.connect(_on_exit)
de7d7cb5 281gdb.events.new_thread.connect(_new_thread)
a18b53a8 282gdb.events.thread_exited.connect(_thread_exited)
de7d7cb5 283gdb.events.cont.connect(_cont)
8a35f6b3 284gdb.events.new_objfile.connect(_new_objfile)
100dbc6d 285gdb.events.free_objfile.connect(_objfile_removed)
c618a1c5 286gdb.events.inferior_call.connect(_on_inferior_call)