10c8a34890ed8d86e5a3041850d40b575a0edc25
[binutils-gdb.git] / gdb / python / lib / gdb / dap / events.py
1 # Copyright 2022-2023 Free Software Foundation, Inc.
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
16 import enum
17 import gdb
18
19 from .server import send_event
20 from .startup import in_gdb_thread, Invoker, log
21 from .modules import is_module, make_module
22
23
24 @in_gdb_thread
25 def _on_exit(event):
26 code = 0
27 if hasattr(event, "exit_code"):
28 code = event.exit_code
29 send_event(
30 "exited",
31 {
32 "exitCode": code,
33 },
34 )
35
36
37 @in_gdb_thread
38 def thread_event(event, reason):
39 send_event(
40 "thread",
41 {
42 "reason": reason,
43 "threadId": event.inferior_thread.global_num,
44 },
45 )
46
47
48 @in_gdb_thread
49 def _new_thread(event):
50 thread_event(event, "started")
51
52
53 @in_gdb_thread
54 def _thread_exited(event):
55 thread_event(event, "exited")
56
57
58 @in_gdb_thread
59 def _new_objfile(event):
60 if is_module(event.new_objfile):
61 send_event(
62 "module",
63 {
64 "reason": "new",
65 "module": make_module(event.new_objfile),
66 },
67 )
68
69
70 _suppress_cont = False
71
72
73 @in_gdb_thread
74 def _cont(event):
75 global _suppress_cont
76 if _suppress_cont:
77 log("_suppress_cont case")
78 _suppress_cont = False
79 else:
80 send_event(
81 "continued",
82 {
83 "threadId": gdb.selected_thread().global_num,
84 "allThreadsContinued": True,
85 },
86 )
87
88
89 class StopKinds(enum.Enum):
90 # The values here are chosen to follow the DAP spec.
91 STEP = "step"
92 BREAKPOINT = "breakpoint"
93 PAUSE = "pause"
94 EXCEPTION = "exception"
95
96
97 _expected_stop = None
98
99
100 @in_gdb_thread
101 def expect_stop(reason):
102 """Indicate that a stop is expected, for the reason given."""
103 global _expected_stop
104 _expected_stop = reason
105
106
107 # A wrapper for Invoker that also sets the expected stop.
108 class ExecutionInvoker(Invoker):
109 """A subclass of Invoker that sets the expected stop.
110 Note that this assumes that the command will restart the inferior,
111 so it will also cause ContinuedEvents to be suppressed."""
112
113 def __init__(self, cmd, expected):
114 super().__init__(cmd)
115 self.expected = expected
116
117 @in_gdb_thread
118 def __call__(self):
119 expect_stop(self.expected)
120 global _suppress_cont
121 _suppress_cont = True
122 # FIXME if the call fails should we clear _suppress_cont?
123 super().__call__()
124
125
126 @in_gdb_thread
127 def _on_stop(event):
128 log("entering _on_stop: " + repr(event))
129 global _expected_stop
130 obj = {
131 "threadId": gdb.selected_thread().global_num,
132 "allThreadsStopped": True,
133 }
134 if isinstance(event, gdb.BreakpointEvent):
135 # Ignore the expected stop, we hit a breakpoint instead.
136 _expected_stop = StopKinds.BREAKPOINT
137 obj["hitBreakpointIds"] = [x.number for x in event.breakpoints]
138 elif _expected_stop is None:
139 # FIXME what is even correct here
140 _expected_stop = StopKinds.EXCEPTION
141 obj["reason"] = _expected_stop.value
142 _expected_stop = None
143 send_event("stopped", obj)
144
145
146 gdb.events.stop.connect(_on_stop)
147 gdb.events.exited.connect(_on_exit)
148 gdb.events.new_thread.connect(_new_thread)
149 gdb.events.thread_exited.connect(_thread_exited)
150 gdb.events.cont.connect(_cont)
151 gdb.events.new_objfile.connect(_new_objfile)