]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blame - gdb/python/lib/gdb/dap/startup.py
Update copyright year range in header of all files managed by GDB
[thirdparty/binutils-gdb.git] / gdb / python / lib / gdb / dap / startup.py
CommitLineData
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
16# Do not import other gdbdap modules here -- this module must come
17# first.
18import functools
19import gdb
20import queue
de7d7cb5
TT
21import threading
22import traceback
954a1f91 23import sys
de7d7cb5 24
dfc4bd46 25from enum import IntEnum, auto
de7d7cb5 26
587a1031
TT
27# Adapt to different Queue types. This is exported for use in other
28# modules as well.
29if sys.version_info[0] == 3 and sys.version_info[1] <= 6:
30 DAPQueue = queue.Queue
31else:
32 DAPQueue = queue.SimpleQueue
33
34
de7d7cb5
TT
35# The GDB thread, aka the main thread.
36_gdb_thread = threading.current_thread()
37
38
39# The DAP thread.
40_dap_thread = None
41
42
2a89c950
TT
43# "Known" exceptions are wrapped in a DAP exception, so that, by
44# default, only rogue exceptions are logged -- this is then used by
45# the test suite.
46class DAPException(Exception):
47 pass
48
49
50# Wrapper for gdb.parse_and_eval that turns exceptions into
51# DAPException.
52def parse_and_eval(expression, global_context=False):
53 try:
54 return gdb.parse_and_eval(expression, global_context=global_context)
55 except Exception as e:
56 # Be sure to preserve the summary, as this can propagate to
57 # the client.
58 raise DAPException(str(e)) from e
59
60
de7d7cb5
TT
61def start_thread(name, target, args=()):
62 """Start a new thread, invoking TARGET with *ARGS there.
63 This is a helper function that ensures that any GDB signals are
64 correctly blocked."""
338b21b0 65 result = gdb.Thread(name=name, target=target, args=args, daemon=True)
560c121c 66 result.start()
de7d7cb5
TT
67
68
69def start_dap(target):
70 """Start the DAP thread and invoke TARGET there."""
de7d7cb5 71 exec_and_log("set breakpoint pending on")
44606912
TT
72
73 # Functions in this thread contain assertions that check for this
74 # global, so we must set it before letting these functions run.
75 def really_start_dap():
76 global _dap_thread
77 _dap_thread = threading.current_thread()
78 target()
79
80 start_thread("DAP", really_start_dap)
de7d7cb5
TT
81
82
83def in_gdb_thread(func):
84 """A decorator that asserts that FUNC must be run in the GDB thread."""
85
86 @functools.wraps(func)
87 def ensure_gdb_thread(*args, **kwargs):
88 assert threading.current_thread() is _gdb_thread
89 return func(*args, **kwargs)
90
91 return ensure_gdb_thread
92
93
94def in_dap_thread(func):
95 """A decorator that asserts that FUNC must be run in the DAP thread."""
96
97 @functools.wraps(func)
98 def ensure_dap_thread(*args, **kwargs):
99 assert threading.current_thread() is _dap_thread
100 return func(*args, **kwargs)
101
102 return ensure_dap_thread
103
104
dfc4bd46
TT
105# Logging levels.
106class LogLevel(IntEnum):
107 DEFAULT = auto()
108 FULL = auto()
109
110
111class LogLevelParam(gdb.Parameter):
112 """DAP logging level."""
113
114 set_doc = "Set the DAP logging level."
115 show_doc = "Show the DAP logging level."
116
117 def __init__(self):
118 super().__init__(
119 "debug dap-log-level", gdb.COMMAND_MAINTENANCE, gdb.PARAM_ZUINTEGER
120 )
121 self.value = LogLevel.DEFAULT
122
123
124_log_level = LogLevelParam()
125
126
de7d7cb5
TT
127class LoggingParam(gdb.Parameter):
128 """Whether DAP logging is enabled."""
129
130 set_doc = "Set the DAP logging status."
131 show_doc = "Show the DAP logging status."
132
133 log_file = None
134
135 def __init__(self):
136 super().__init__(
137 "debug dap-log-file", gdb.COMMAND_MAINTENANCE, gdb.PARAM_OPTIONAL_FILENAME
138 )
139 self.value = None
140
141 def get_set_string(self):
142 # Close any existing log file, no matter what.
143 if self.log_file is not None:
144 self.log_file.close()
145 self.log_file = None
146 if self.value is not None:
147 self.log_file = open(self.value, "w")
148 return ""
149
150
151dap_log = LoggingParam()
152
153
dfc4bd46 154def log(something, level=LogLevel.DEFAULT):
de7d7cb5 155 """Log SOMETHING to the log file, if logging is enabled."""
dfc4bd46 156 if dap_log.log_file is not None and level <= _log_level.value:
de7d7cb5
TT
157 print(something, file=dap_log.log_file)
158 dap_log.log_file.flush()
159
160
dfc4bd46 161def log_stack(level=LogLevel.DEFAULT):
de7d7cb5 162 """Log a stack trace to the log file, if logging is enabled."""
dfc4bd46 163 if dap_log.log_file is not None and level <= _log_level.value:
de7d7cb5
TT
164 traceback.print_exc(file=dap_log.log_file)
165
166
167@in_gdb_thread
168def exec_and_log(cmd):
169 """Execute the gdb command CMD.
170 If logging is enabled, log the command and its output."""
171 log("+++ " + cmd)
172 try:
173 output = gdb.execute(cmd, from_tty=True, to_string=True)
174 if output != "":
175 log(">>> " + output)
176 except gdb.error:
177 log_stack()
178
179
180class Invoker(object):
181 """A simple class that can invoke a gdb command."""
182
183 def __init__(self, cmd):
184 self.cmd = cmd
185
186 # This is invoked in the gdb thread to run the command.
187 @in_gdb_thread
188 def __call__(self):
189 exec_and_log(self.cmd)
190
191
192def send_gdb(cmd):
193 """Send CMD to the gdb thread.
194 CMD can be either a function or a string.
195 If it is a string, it is passed to gdb.execute."""
196 if isinstance(cmd, str):
197 cmd = Invoker(cmd)
198 gdb.post_event(cmd)
199
200
201def send_gdb_with_response(fn):
202 """Send FN to the gdb thread and return its result.
203 If FN is a string, it is passed to gdb.execute and None is
204 returned as the result.
205 If FN throws an exception, this function will throw the
206 same exception in the calling thread.
207 """
208 if isinstance(fn, str):
209 fn = Invoker(fn)
587a1031 210 result_q = DAPQueue()
de7d7cb5
TT
211
212 def message():
213 try:
214 val = fn()
215 result_q.put(val)
c0a652c2 216 except (Exception, KeyboardInterrupt) as e:
de7d7cb5
TT
217 result_q.put(e)
218
219 send_gdb(message)
220 val = result_q.get()
c0a652c2 221 if isinstance(val, (Exception, KeyboardInterrupt)):
de7d7cb5
TT
222 raise val
223 return val