diff options
Diffstat (limited to 'gdb/python')
30 files changed, 433 insertions, 236 deletions
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 4ea5d06..cedd897 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -19,17 +19,22 @@ import sys import threading import traceback from contextlib import contextmanager +from importlib import reload -# Python 3 moved "reload" -if sys.version_info >= (3, 4): - from importlib import reload -else: - from imp import reload - -import _gdb - +# The star import imports _gdb names. When the names are used locally, they +# trigger F405 warnings unless added to the explicit import list. # Note that two indicators are needed here to silence flake8. from _gdb import * # noqa: F401,F403 +from _gdb import ( + STDERR, + STDOUT, + Command, + execute, + flush, + parameter, + selected_inferior, + write, +) # isort: split @@ -60,14 +65,14 @@ class _GdbFile(object): self.write(line) def flush(self): - _gdb.flush(stream=self.stream) + flush(stream=self.stream) def write(self, s): - _gdb.write(s, stream=self.stream) + write(s, stream=self.stream) -sys.stdout = _GdbFile(_gdb.STDOUT) -sys.stderr = _GdbFile(_gdb.STDERR) +sys.stdout = _GdbFile(STDOUT) +sys.stderr = _GdbFile(STDERR) # Default prompt hook does nothing. prompt_hook = None @@ -189,7 +194,7 @@ def GdbSetPythonDirectory(dir): def current_progspace(): "Return the current Progspace." - return _gdb.selected_inferior().progspace + return selected_inferior().progspace def objfiles(): @@ -226,14 +231,14 @@ def set_parameter(name, value): value = "on" else: value = "off" - _gdb.execute("set " + name + " " + str(value), to_string=True) + execute("set " + name + " " + str(value), to_string=True) @contextmanager def with_parameter(name, value): """Temporarily set the GDB parameter NAME to VALUE. Note that this is a context manager.""" - old_value = _gdb.parameter(name) + old_value = parameter(name) set_parameter(name, value) try: # Nothing that useful to return. @@ -392,3 +397,121 @@ def _handle_missing_objfile(pspace, buildid, filename): return _handle_missing_files( pspace, "objfile", lambda h: h(pspace, buildid, filename) ) + + +class ParameterPrefix: + # A wrapper around gdb.Command for creating set/show prefixes. + # + # When creating a gdb.Parameter sub-classes, it is sometimes necessary + # to first create a gdb.Command object in order to create the needed + # command prefix. However, for parameters, we actually need two + # prefixes, a 'set' prefix, and a 'show' prefix. With this helper + # class, a single instance of this class will create both prefixes at + # once. + # + # It is important that this class-level documentation not be a __doc__ + # string. Users are expected to sub-class this ParameterPrefix class + # and add their own documentation. If they don't, then GDB will + # generate a suitable doc string. But, if this (parent) class has a + # __doc__ string of its own, then sub-classes will inherit that __doc__ + # string, and GDB will not understand that it needs to generate one. + + class _PrefixCommand(Command): + """A gdb.Command used to implement both the set and show prefixes. + + This documentation string is not used as the prefix command + documentation as it is overridden in the __init__ method below.""" + + # This private method is connected to the 'invoke' attribute within + # this _PrefixCommand object if the containing ParameterPrefix + # object has an invoke_set or invoke_show method. + # + # This method records within self.__delegate which _PrefixCommand + # object is currently active, and then calls the correct invoke + # method on the delegat object (the ParameterPrefix sub-class + # object). + # + # Recording the currently active _PrefixCommand object is important; + # if from the invoke method the user calls dont_repeat, then this is + # forwarded to the currently active _PrefixCommand object. + def __invoke(self, args, from_tty): + + # A helper class for use as part of a Python 'with' block. + # Records which gdb.Command object is currently running its + # invoke method. + class MarkActiveCallback: + # The CMD is a _PrefixCommand object, and the DELEGATE is + # the ParameterPrefix class, or sub-class object. At this + # point we simple record both of these within the + # MarkActiveCallback object. + def __init__(self, cmd, delegate): + self.__cmd = cmd + self.__delegate = delegate + + # Record the currently active _PrefixCommand object within + # the outer ParameterPrefix sub-class object. + def __enter__(self): + self.__delegate.active_prefix = self.__cmd + + # Once the invoke method has completed, then clear the + # _PrefixCommand object that was stored into the outer + # ParameterPrefix sub-class object. + def __exit__(self, exception_type, exception_value, traceback): + self.__delegate.active_prefix = None + + # The self.__cb attribute is set when the _PrefixCommand object + # is created, and is either invoke_set or invoke_show within the + # ParameterPrefix sub-class object. + assert callable(self.__cb) + + # Record the currently active _PrefixCommand object within the + # ParameterPrefix sub-class object, then call the relevant + # invoke method within the ParameterPrefix sub-class object. + with MarkActiveCallback(self, self.__delegate): + self.__cb(args, from_tty) + + @staticmethod + def __find_callback(delegate, mode): + """The MODE is either 'set' or 'show'. Look for an invoke_MODE method + on DELEGATE, if a suitable method is found, then return it, otherwise, + return None. + """ + cb = getattr(delegate, "invoke_" + mode, None) + if callable(cb): + return cb + return None + + def __init__(self, mode, name, cmd_class, delegate, doc=None): + """Setup this gdb.Command. Mode is a string, either 'set' or 'show'. + NAME is the name for this prefix command, that is, the + words that appear after both 'set' and 'show' in the + command name. CMD_CLASS is the usual enum. And DELEGATE + is the gdb.ParameterPrefix object this prefix is part of. + """ + assert mode == "set" or mode == "show" + if doc is None: + self.__doc__ = delegate.__doc__ + else: + self.__doc__ = doc + self.__cb = self.__find_callback(delegate, mode) + self.__delegate = delegate + if self.__cb is not None: + self.invoke = self.__invoke + super().__init__(mode + " " + name, cmd_class, prefix=True) + + def __init__(self, name, cmd_class, doc=None): + """Create a _PrefixCommand for both the set and show prefix commands. + NAME is the command name without either the leading 'set ' or + 'show ' strings, and CMD_CLASS is the usual enum value. + """ + self.active_prefix = None + self._set_prefix_cmd = self._PrefixCommand("set", name, cmd_class, self, doc) + self._show_prefix_cmd = self._PrefixCommand("show", name, cmd_class, self, doc) + + # When called from within an invoke method the self.active_prefix + # attribute should be set to a gdb.Command sub-class (a _PrefixCommand + # object, see above). Forward the dont_repeat call to this object to + # register the actual command as none repeating. + def dont_repeat(self): + if self.active_prefix is not None: + self.active_prefix.dont_repeat() diff --git a/gdb/python/lib/gdb/dap/__init__.py b/gdb/python/lib/gdb/dap/__init__.py index f908c91..1c3cf8e 100644 --- a/gdb/python/lib/gdb/dap/__init__.py +++ b/gdb/python/lib/gdb/dap/__init__.py @@ -96,5 +96,4 @@ def pre_command_loop(): # These are handy for bug reports. startup.exec_and_log("show version") startup.exec_and_log("show configuration") - global server startup.start_dap(server.main_loop) diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py index bfb7bd3..4d4ca18 100644 --- a/gdb/python/lib/gdb/dap/breakpoint.py +++ b/gdb/python/lib/gdb/dap/breakpoint.py @@ -51,7 +51,6 @@ def suppress_new_breakpoint_event(): @in_gdb_thread def _bp_modified(event): - global _suppress_bp if not _suppress_bp: send_event( "breakpoint", @@ -64,7 +63,6 @@ def _bp_modified(event): @in_gdb_thread def _bp_created(event): - global _suppress_bp if not _suppress_bp: send_event( "breakpoint", @@ -77,7 +75,6 @@ def _bp_created(event): @in_gdb_thread def _bp_deleted(event): - global _suppress_bp if not _suppress_bp: send_event( "breakpoint", @@ -151,7 +148,6 @@ def _remove_entries(table, *names): # the breakpoint. @in_gdb_thread def _set_breakpoints_callback(kind, specs, creator): - global breakpoint_map # Try to reuse existing breakpoints if possible. if kind in breakpoint_map: saved_map = breakpoint_map[kind] @@ -330,7 +326,7 @@ def _rewrite_fn_breakpoint( } -@request("setFunctionBreakpoints") +@request("setFunctionBreakpoints", expect_stopped=False) @capability("supportsFunctionBreakpoints") def set_fn_breakpoint(*, breakpoints: Sequence, **args): specs = [_rewrite_fn_breakpoint(**bp) for bp in breakpoints] @@ -363,7 +359,7 @@ def _rewrite_insn_breakpoint( } -@request("setInstructionBreakpoints") +@request("setInstructionBreakpoints", expect_stopped=False) @capability("supportsInstructionBreakpoints") def set_insn_breakpoints( *, breakpoints: Sequence, offset: Optional[int] = None, **args @@ -414,7 +410,7 @@ def _rewrite_exception_breakpoint( } -@request("setExceptionBreakpoints") +@request("setExceptionBreakpoints", expect_stopped=False) @capability("supportsExceptionFilterOptions") @capability( "exceptionBreakpointFilters", diff --git a/gdb/python/lib/gdb/dap/completions.py b/gdb/python/lib/gdb/dap/completions.py index 85acc43..e5003ff 100644 --- a/gdb/python/lib/gdb/dap/completions.py +++ b/gdb/python/lib/gdb/dap/completions.py @@ -39,8 +39,11 @@ def completions( line = 1 else: line = import_line(line) - text = text.splitlines()[line - 1] - text = text[: column - 1] + if text: + text = text.splitlines()[line - 1] + text = text[: column - 1] + else: + text = "" mi_result = exec_mi_and_log("-complete", text) result = [] completion = None diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py index 55538e6..fcbcc99 100644 --- a/gdb/python/lib/gdb/dap/evaluate.py +++ b/gdb/python/lib/gdb/dap/evaluate.py @@ -69,7 +69,7 @@ def _repl(command, frame_id): } -@request("evaluate") +@request("evaluate", defer_events=False) @capability("supportsEvaluateForHovers") @capability("supportsValueFormattingOptions") def eval_request( @@ -110,7 +110,7 @@ def variables( @capability("supportsSetExpression") -@request("setExpression") +@request("setExpression", defer_events=False) def set_expression( *, expression: str, value: str, frameId: Optional[int] = None, format=None, **args ): @@ -126,7 +126,7 @@ def set_expression( @capability("supportsSetVariable") -@request("setVariable") +@request("setVariable", defer_events=False) def set_variable( *, variablesReference: int, name: str, value: str, format=None, **args ): diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py index 54caea0..e8f2655 100644 --- a/gdb/python/lib/gdb/dap/events.py +++ b/gdb/python/lib/gdb/dap/events.py @@ -17,12 +17,12 @@ import gdb from .modules import is_module, make_module from .scopes import set_finish_value -from .server import send_event, send_event_maybe_later +from .server import send_event from .startup import exec_and_log, in_gdb_thread, log # True when the inferior is thought to be running, False otherwise. # This may be accessed from any thread, which can be racy. However, -# this unimportant because this global is only used for the +# this is unimportant because this global is only used for the # 'notStopped' response, which itself is inherently racy. inferior_running = False @@ -238,10 +238,9 @@ def _on_stop(event): ): obj["reason"] = "pause" else: - global stop_reason_map obj["reason"] = stop_reason_map[event.details["reason"]] _expected_pause = False - send_event_maybe_later("stopped", obj) + send_event("stopped", obj) # This keeps a bit of state between the start of an inferior call and diff --git a/gdb/python/lib/gdb/dap/frames.py b/gdb/python/lib/gdb/dap/frames.py index 6433dbe..4dacb87 100644 --- a/gdb/python/lib/gdb/dap/frames.py +++ b/gdb/python/lib/gdb/dap/frames.py @@ -53,12 +53,11 @@ gdb.events.cont.connect(_clear_frame_ids) @in_gdb_thread def frame_for_id(id): """Given a frame identifier ID, return the corresponding frame.""" - global thread_ids if id in thread_ids: thread_id = thread_ids[id] if thread_id != gdb.selected_thread().global_num: set_thread(thread_id) - global _all_frames + return _all_frames[id] @@ -103,10 +102,8 @@ def _frame_id_generator(): # Helper function to assign an ID to a frame. def get_id(frame): - global _all_frames num = len(_all_frames) _all_frames.append(frame) - global thread_ids thread_ids[num] = gdb.selected_thread().global_num return num @@ -128,7 +125,6 @@ def _frame_id_generator(): @in_gdb_thread def _get_frame_iterator(): thread_id = gdb.selected_thread().global_num - global _iter_map if thread_id not in _iter_map: _iter_map[thread_id] = _MemoizingIterator(_frame_id_generator()) return _iter_map[thread_id] diff --git a/gdb/python/lib/gdb/dap/globalvars.py b/gdb/python/lib/gdb/dap/globalvars.py index 2e4b2a6..9d64d28 100644 --- a/gdb/python/lib/gdb/dap/globalvars.py +++ b/gdb/python/lib/gdb/dap/globalvars.py @@ -78,7 +78,6 @@ def get_global_scope(frame): except RuntimeError: return None - global _id_to_scope block = block.static_block if block in _id_to_scope: return _id_to_scope[block] diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py index e093e60..8ac4c77 100644 --- a/gdb/python/lib/gdb/dap/launch.py +++ b/gdb/python/lib/gdb/dap/launch.py @@ -164,7 +164,6 @@ def attach( @request("configurationDone", on_dap_thread=True) def config_done(**args): # Handle the launch or attach. - global _launch_or_attach_promise if _launch_or_attach_promise is None: raise DAPException("launch or attach not specified") # Resolve the launch or attach, but only after the diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py index ddf4e05..898fff1 100644 --- a/gdb/python/lib/gdb/dap/next.py +++ b/gdb/python/lib/gdb/dap/next.py @@ -16,7 +16,7 @@ import gdb from .events import exec_and_expect_stop -from .server import capability, request, send_gdb, send_gdb_with_response +from .server import capability, request from .startup import in_gdb_thread from .state import set_thread @@ -73,19 +73,14 @@ def step_in( exec_and_expect_stop(cmd) -@request("stepOut", defer_stop_events=True) +@request("stepOut") def step_out(*, threadId: int, singleThread: bool = False, **args): _handle_thread_step(threadId, singleThread, True) exec_and_expect_stop("finish &", propagate_exception=True) -# This is a server-side request because it is funny: it wants to -# 'continue' but also return a result, which precludes using -# response=False. Using 'continue &' would mostly work ok, but this -# yields races when a stop occurs before the response is sent back to -# the client. -@request("continue", on_dap_thread=True) +@request("continue") def continue_request(*, threadId: int, singleThread: bool = False, **args): - locked = send_gdb_with_response(lambda: _handle_thread_step(threadId, singleThread)) - send_gdb(lambda: exec_and_expect_stop("continue")) + locked = _handle_thread_step(threadId, singleThread) + exec_and_expect_stop("continue &") return {"allThreadsContinued": not locked} diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py index f499dcb..7ce3a7f 100644 --- a/gdb/python/lib/gdb/dap/scopes.py +++ b/gdb/python/lib/gdb/dap/scopes.py @@ -120,7 +120,6 @@ class _FinishScopeReference(_ScopeReference): def fetch_one_child(self, idx): assert idx == 0 - global _last_return_value return ("(return)", _last_return_value) @@ -145,8 +144,6 @@ class _RegisterReference(_ScopeReference): @request("scopes") def scopes(*, frameId: int, **extra): - global _last_return_value - global frame_to_scope if frameId in frame_to_scope: scopes = frame_to_scope[frameId] else: diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py index d4314e8..7dab582 100644 --- a/gdb/python/lib/gdb/dap/server.py +++ b/gdb/python/lib/gdb/dap/server.py @@ -74,6 +74,13 @@ class DeferredRequest: self._result = result @in_dap_thread + def defer_events(self): + """Return True if events should be deferred during execution. + + This may be overridden by subclasses.""" + return True + + @in_dap_thread def invoke(self): """Implement the deferred request. @@ -95,7 +102,10 @@ class DeferredRequest: """ with _server.canceller.current_request(self._req): + if self.defer_events(): + _server.set_defer_events() _server.invoke_request(self._req, self._result, self.invoke) + _server.emit_pending_events() # A subclass of Exception that is used solely for reporting that a @@ -230,7 +240,7 @@ class Server: self._out_stream = out_stream self._child_stream = child_stream self._delayed_fns_lock = threading.Lock() - self.defer_stop_events = False + self._defer_events = False self._delayed_fns = [] # This queue accepts JSON objects that are then sent to the # DAP client. Writing is done in a separate thread to avoid @@ -307,7 +317,6 @@ class Server: args = {} def fn(): - global _commands return _commands[params["command"]](**args) self.invoke_request(req, result, fn) @@ -317,7 +326,7 @@ class Server: def _read_inferior_output(self): while True: line = self._child_stream.readline() - self.send_event( + self.send_event_maybe_later( "output", { "category": "stdout", @@ -357,6 +366,17 @@ class Server: self._read_queue.put(None) @in_dap_thread + def emit_pending_events(self): + """Emit any pending events.""" + fns = None + with self._delayed_fns_lock: + fns = self._delayed_fns + self._delayed_fns = [] + self._defer_events = False + for fn in fns: + fn() + + @in_dap_thread def main_loop(self): """The main loop of the DAP server.""" # Before looping, start the thread that writes JSON to the @@ -372,13 +392,7 @@ class Server: req = cmd["seq"] with self.canceller.current_request(req): self._handle_command(cmd) - fns = None - with self._delayed_fns_lock: - fns = self._delayed_fns - self._delayed_fns = [] - self.defer_stop_events = False - for fn in fns: - fn() + self.emit_pending_events() # Got the terminate request. This is handled by the # JSON-writing thread, so that we can ensure that all # responses are flushed to the client before exiting. @@ -387,13 +401,13 @@ class Server: send_gdb("quit") @in_dap_thread - def send_event_later(self, event, body=None): - """Send a DAP event back to the client, but only after the - current request has completed.""" + def set_defer_events(self): + """Defer any events until the current request has completed.""" with self._delayed_fns_lock: - self._delayed_fns.append(lambda: self.send_event(event, body)) + self._defer_events = True - @in_gdb_thread + # Note that this does not need to be run in any particular thread, + # because it uses locks for thread-safety. def send_event_maybe_later(self, event, body=None): """Send a DAP event back to the client, but if a request is in-flight within the dap thread and that request is configured to delay the event, @@ -402,10 +416,10 @@ class Server: with self.canceller.lock: if self.canceller.in_flight_dap_thread: with self._delayed_fns_lock: - if self.defer_stop_events: - self._delayed_fns.append(lambda: self.send_event(event, body)) + if self._defer_events: + self._delayed_fns.append(lambda: self._send_event(event, body)) return - self.send_event(event, body) + self._send_event(event, body) @in_dap_thread def call_function_later(self, fn): @@ -416,7 +430,7 @@ class Server: # Note that this does not need to be run in any particular thread, # because it just creates an object and writes it to a thread-safe # queue. - def send_event(self, event, body=None): + def _send_event(self, event, body=None): """Send an event to the DAP client. EVENT is the name of the event, a string. BODY is the body of the event, an arbitrary object.""" @@ -440,22 +454,11 @@ def send_event(event, body=None): """Send an event to the DAP client. EVENT is the name of the event, a string. BODY is the body of the event, an arbitrary object.""" - global _server - _server.send_event(event, body) - - -def send_event_maybe_later(event, body=None): - """Send a DAP event back to the client, but if a request is in-flight - within the dap thread and that request is configured to delay the event, - wait until the response has been sent until the event is sent back to - the client.""" - global _server _server.send_event_maybe_later(event, body) def call_function_later(fn): """Call FN later -- after the current request's response has been sent.""" - global _server _server.call_function_later(fn) @@ -480,7 +483,7 @@ def request( response: bool = True, on_dap_thread: bool = False, expect_stopped: bool = True, - defer_stop_events: bool = False + defer_events: bool = True ): """A decorator for DAP requests. @@ -502,9 +505,9 @@ def request( inferior is running. When EXPECT_STOPPED is False, the request will proceed regardless of the inferior's state. - If DEFER_STOP_EVENTS is True, then make sure any stop events sent - during the request processing are not sent to the client until the - response has been sent. + If DEFER_EVENTS is True, then make sure any events sent during the + request processing are not sent to the client until the response + has been sent. """ # Validate the parameters. @@ -527,27 +530,33 @@ def request( # Verify that the function is run on the correct thread. if on_dap_thread: - cmd = in_dap_thread(func) + check_cmd = in_dap_thread(func) else: func = in_gdb_thread(func) if response: - if defer_stop_events: - global _server - if _server is not None: - with _server.delayed_events_lock: - _server.defer_stop_events = True def sync_call(**args): return send_gdb_with_response(lambda: func(**args)) - cmd = sync_call + check_cmd = sync_call else: def non_sync_call(**args): return send_gdb(lambda: func(**args)) - cmd = non_sync_call + check_cmd = non_sync_call + + if defer_events: + + def deferring(**args): + _server.set_defer_events() + return check_cmd(**args) + + cmd = deferring + + else: + cmd = check_cmd # If needed, check that the inferior is not running. This # wrapping is done last, so the check is done first, before @@ -555,7 +564,6 @@ def request( if expect_stopped: cmd = _check_not_running(cmd) - global _commands assert name not in _commands _commands[name] = cmd return cmd @@ -568,7 +576,6 @@ def capability(name, value=True): the DAP capability NAME.""" def wrap(func): - global _capabilities assert name not in _capabilities _capabilities[name] = value return func @@ -581,7 +588,6 @@ def client_bool_capability(name, default=False): If the capability was not specified, or did not have boolean type, DEFAULT is returned. DEFAULT defaults to False.""" - global _server if name in _server.config and isinstance(_server.config[name], bool): return _server.config[name] return default @@ -589,9 +595,8 @@ def client_bool_capability(name, default=False): @request("initialize", on_dap_thread=True) def initialize(**args): - global _server, _capabilities _server.config = args - _server.send_event_later("initialized") + _server.send_event_maybe_later("initialized") global _lines_start_at_1 _lines_start_at_1 = client_bool_capability("linesStartAt1", True) global _columns_start_at_1 @@ -705,7 +710,6 @@ def export_line(line: int) -> int: """Rewrite LINE according to client capability. This applies the linesStartAt1 capability as needed, when sending a line number from gdb to the client.""" - global _lines_start_at_1 if not _lines_start_at_1: # In gdb, lines start at 1, so we only need to change this if # the client starts at 0. @@ -717,7 +721,6 @@ def import_line(line: int) -> int: """Rewrite LINE according to client capability. This applies the linesStartAt1 capability as needed, when the client sends a line number to gdb.""" - global _lines_start_at_1 if not _lines_start_at_1: # In gdb, lines start at 1, so we only need to change this if # the client starts at 0. diff --git a/gdb/python/lib/gdb/dap/sources.py b/gdb/python/lib/gdb/dap/sources.py index 938c93a..efcd799 100644 --- a/gdb/python/lib/gdb/dap/sources.py +++ b/gdb/python/lib/gdb/dap/sources.py @@ -37,7 +37,6 @@ def make_source(fullname, filename=None): FILENAME is the base name; if None (the default), then it is computed from FULLNAME. """ - global _source_map if fullname in _source_map: result = _source_map[fullname] else: @@ -53,7 +52,6 @@ def make_source(fullname, filename=None): global _next_source result["sourceReference"] = _next_source - global _id_map _id_map[_next_source] = result _next_source += 1 @@ -66,12 +64,11 @@ def decode_source(source): """Decode a Source object. Finds and returns the filename of a given Source object.""" - if "path" in source: - return source["path"] - if "sourceReference" not in source: + if "sourceReference" not in source or source["sourceReference"] <= 0: + if "path" in source: + return source["path"] raise DAPException("either 'path' or 'sourceReference' must appear in Source") ref = source["sourceReference"] - global _id_map if ref not in _id_map: raise DAPException("no sourceReference " + str(ref)) return _id_map[ref]["path"] diff --git a/gdb/python/lib/gdb/dap/threads.py b/gdb/python/lib/gdb/dap/threads.py index c271961..89046a8 100644 --- a/gdb/python/lib/gdb/dap/threads.py +++ b/gdb/python/lib/gdb/dap/threads.py @@ -16,27 +16,32 @@ import gdb from .server import request +from .startup import in_gdb_thread +@in_gdb_thread def _thread_name(thr): if thr.name is not None: return thr.name if thr.details is not None: return thr.details - return None + # Always return a name, as the protocol doesn't allow for nameless + # threads. Use the local thread number here... it doesn't matter + # without multi-inferior but in that case it might make more + # sense. + return f"Thread #{thr.num}" -@request("threads") +@request("threads", expect_stopped=False) def threads(**args): result = [] for thr in gdb.selected_inferior().threads(): - one_result = { - "id": thr.global_num, - } - name = _thread_name(thr) - if name is not None: - one_result["name"] = name - result.append(one_result) + result.append( + { + "id": thr.global_num, + "name": _thread_name(thr), + } + ) return { "threads": result, } diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py index 42b5302..8a13c51 100644 --- a/gdb/python/lib/gdb/dap/varref.py +++ b/gdb/python/lib/gdb/dap/varref.py @@ -69,7 +69,6 @@ class BaseReference(ABC): NAME is a string or None. None means this does not have a name, e.g., the result of expression evaluation.""" - global all_variables all_variables.append(self) self._ref = len(all_variables) self._name = name @@ -267,7 +266,7 @@ class VariableReference(BaseReference): @in_gdb_thread def find_variable(ref): """Given a variable reference, return the corresponding variable object.""" - global all_variables + # Variable references are offset by 1. ref = ref - 1 if ref < 0 or ref > len(all_variables): diff --git a/gdb/python/lib/gdb/styling.py b/gdb/python/lib/gdb/styling.py index e9387e3..60c470f 100644 --- a/gdb/python/lib/gdb/styling.py +++ b/gdb/python/lib/gdb/styling.py @@ -80,7 +80,6 @@ try: # ignore. pass - global _asm_lexers if lexer_type not in _asm_lexers: _asm_lexers[lexer_type] = lexers.get_lexer_by_name(lexer_type) _asm_lexers[lexer_type].add_filter(HandleNasmComments()) diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index 861e9a3..9ce8671 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -948,7 +948,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) else { PyErr_SetString (PyExc_RuntimeError, - _("Line keyword should be an integer or a string. ")); + _("Line keyword should be an integer or a string.")); return -1; } } @@ -1537,9 +1537,7 @@ PyTypeObject breakpoint_object_type = 0, /* tp_alloc */ }; -void _initialize_py_breakpoint (); -void -_initialize_py_breakpoint () +INIT_GDB_FILE (py_breakpoint) { add_setshow_boolean_cmd ("py-breakpoint", class_maintenance, &pybp_debug, diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index 5d98d03..dc5e270 100644 --- a/gdb/python/py-cmd.c +++ b/gdb/python/py-cmd.c @@ -105,19 +105,17 @@ cmdpy_function (const char *args, int from_tty, cmd_list_element *command) gdbpy_enter enter_py; - if (! obj) + if (obj == nullptr) error (_("Invalid invocation of Python command object.")); - if (! PyObject_HasAttr ((PyObject *) obj, invoke_cst)) - { - if (obj->command->is_prefix ()) - { - /* A prefix command does not need an invoke method. */ - return; - } - error (_("Python command object missing 'invoke' method.")); - } - if (! args) + /* If we get here for a prefix command then the prefix command had an + 'invoke' method when it was created. If the 'invoke' method is now + missing, then the user has done something weird (like deleting the + invoke method, yuck!). */ + if (!PyObject_HasAttr ((PyObject *) obj, invoke_cst)) + error (_("Python command object missing 'invoke' method.")); + + if (args == nullptr) args = ""; gdbpy_ref<> argobj (PyUnicode_Decode (args, strlen (args), host_charset (), NULL)); @@ -336,24 +334,30 @@ cmdpy_completer (struct cmd_list_element *command, name of the new command. All earlier words must be existing prefix commands. - *BASE_LIST is set to the final prefix command's list of - *sub-commands. + *BASE_LIST is set to the final prefix command's list of sub-commands. START_LIST is the list in which the search starts. + When PREFIX_CMD is not NULL then *PREFIX_CMD is set to the prefix + command itself, or NULL, if there is no prefix command. + This function returns the name of the new command. On error sets the Python error and returns NULL. */ gdb::unique_xmalloc_ptr<char> gdbpy_parse_command_name (const char *name, struct cmd_list_element ***base_list, - struct cmd_list_element **start_list) + struct cmd_list_element **start_list, + struct cmd_list_element **prefix_cmd) { struct cmd_list_element *elt; int len = strlen (name); int i, lastchar; const char *prefix_text2; + if (prefix_cmd != nullptr) + *prefix_cmd = nullptr; + /* Skip trailing whitespace. */ for (i = len - 1; i >= 0 && (name[i] == ' ' || name[i] == '\t'); --i) ; @@ -368,9 +372,8 @@ gdbpy_parse_command_name (const char *name, for (; i > 0 && valid_cmd_char_p (name[i - 1]); --i) ; - gdb::unique_xmalloc_ptr<char> result ((char *) xmalloc (lastchar - i + 2)); - memcpy (result.get (), &name[i], lastchar - i + 1); - result.get ()[lastchar - i + 1] = '\0'; + gdb::unique_xmalloc_ptr<char> result + = make_unique_xstrndup (&name[i], lastchar - i + 1); /* Skip whitespace again. */ for (--i; i >= 0 && (name[i] == ' ' || name[i] == '\t'); --i) @@ -385,7 +388,7 @@ gdbpy_parse_command_name (const char *name, prefix_text2 = prefix_text.c_str (); elt = lookup_cmd_1 (&prefix_text2, *start_list, NULL, NULL, 1); - if (elt == NULL || elt == CMD_LIST_AMBIGUOUS) + if (elt == nullptr || elt == CMD_LIST_AMBIGUOUS || *prefix_text2 != '\0') { PyErr_Format (PyExc_RuntimeError, _("Could not find command prefix %s."), prefix_text.c_str ()); @@ -395,6 +398,8 @@ gdbpy_parse_command_name (const char *name, if (elt->is_prefix ()) { *base_list = elt->subcommands; + if (prefix_cmd != nullptr) + *prefix_cmd = elt; return result; } @@ -469,8 +474,9 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) return -1; } + cmd_list_element *prefix_cmd = nullptr; gdb::unique_xmalloc_ptr<char> cmd_name - = gdbpy_parse_command_name (name, &cmd_list, &cmdlist); + = gdbpy_parse_command_name (name, &cmd_list, &cmdlist, &prefix_cmd); if (cmd_name == nullptr) return -1; @@ -507,26 +513,64 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) if (is_prefix) { - int allow_unknown; - - /* If we have our own "invoke" method, then allow unknown - sub-commands. */ - allow_unknown = PyObject_HasAttr (self, invoke_cst); - cmd = add_prefix_cmd (cmd_name.get (), - (enum command_class) cmdtype, - NULL, docstring.release (), &obj->sub_list, - allow_unknown, cmd_list); + bool has_invoke = PyObject_HasAttr (self, invoke_cst) == 1; + if (has_invoke) + { + /* If there's an 'invoke' method, then create the prefix + command, but call cmdpy_function to dispatch to the invoke + method when the user runs the prefix with no sub-command. */ + cmd = add_prefix_cmd (cmd_name.get (), + (enum command_class) cmdtype, + nullptr, + docstring.release (), &obj->sub_list, + 1 /* allow_unknown */, cmd_list); + cmd->func = cmdpy_function; + } + else + { + /* If there is no 'invoke' method, then create the prefix + using the standard prefix callbacks. This means that for + 'set prefix' the user will get the help text listing all + of the sub-commands, and for 'show prefix', the user will + see all of the sub-command values. */ + if (prefix_cmd != nullptr) + { + while (prefix_cmd->prefix != nullptr) + prefix_cmd = prefix_cmd->prefix; + } + + bool is_show = (prefix_cmd != nullptr + && prefix_cmd->subcommands == &showlist); + + if (is_show) + cmd = add_show_prefix_cmd (cmd_name.get (), + (enum command_class) cmdtype, + docstring.release (), + &obj->sub_list, + 0 /* allow_unknown */, cmd_list); + else + cmd = add_basic_prefix_cmd (cmd_name.get (), + (enum command_class) cmdtype, + docstring.release (), + &obj->sub_list, + 0 /* allow_unknown */, cmd_list); + } } else - cmd = add_cmd (cmd_name.get (), (enum command_class) cmdtype, - docstring.release (), cmd_list); + { + /* For non-prefix commands, arrange to call cmdpy_function, which + invokes the Python 'invoke' method, or raises an exception if + the 'invoke' method is missing. */ + cmd = add_cmd (cmd_name.get (), (enum command_class) cmdtype, + docstring.release (), cmd_list); + cmd->func = cmdpy_function; + } /* If successful, the above takes ownership of the name, since we set name_allocated, so release it. */ cmd_name.release (); - /* There appears to be no API to set this. */ - cmd->func = cmdpy_function; + /* There appears to be no API to set these member variables. */ cmd->destroyer = cmdpy_destroyer; cmd->doc_allocated = 1; cmd->name_allocated = 1; diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c index 91ad7ae..3bbd22d 100644 --- a/gdb/python/py-color.c +++ b/gdb/python/py-color.c @@ -21,6 +21,7 @@ #include "python-internal.h" #include "py-color.h" #include "cli/cli-decode.h" +#include "cli/cli-style.h" /* Colorspace constants and their values. */ static struct { @@ -64,7 +65,8 @@ create_color_object (const ui_file_style::color &color) bool gdbpy_is_color (PyObject *obj) { - return PyObject_IsInstance (obj, (PyObject *) &colorpy_object_type); + gdb_assert (obj != nullptr); + return PyObject_TypeCheck (obj, &colorpy_object_type) != 0; } /* See py-color.h. */ @@ -80,33 +82,33 @@ gdbpy_get_color (PyObject *obj) static PyObject * get_attr (PyObject *obj, PyObject *attr_name) { - if (! PyUnicode_Check (attr_name)) + if (!PyUnicode_Check (attr_name)) return PyObject_GenericGetAttr (obj, attr_name); colorpy_object *self = (colorpy_object *) obj; const ui_file_style::color &color = self->color; - if (! PyUnicode_CompareWithASCIIString (attr_name, "colorspace")) + if (!PyUnicode_CompareWithASCIIString (attr_name, "colorspace")) { int value = static_cast<int> (color.colorspace ()); return gdb_py_object_from_longest (value).release (); } - if (! PyUnicode_CompareWithASCIIString (attr_name, "is_none")) + if (!PyUnicode_CompareWithASCIIString (attr_name, "is_none")) return PyBool_FromLong (color.is_none ()); - if (! PyUnicode_CompareWithASCIIString (attr_name, "is_indexed")) + if (!PyUnicode_CompareWithASCIIString (attr_name, "is_indexed")) return PyBool_FromLong (color.is_indexed ()); - if (! PyUnicode_CompareWithASCIIString (attr_name, "is_direct")) + if (!PyUnicode_CompareWithASCIIString (attr_name, "is_direct")) return PyBool_FromLong (color.is_direct ()); if (color.is_indexed () - && ! PyUnicode_CompareWithASCIIString (attr_name, "index")) + && !PyUnicode_CompareWithASCIIString (attr_name, "index")) return gdb_py_object_from_longest (color.get_value ()).release (); if (color.is_direct () - && ! PyUnicode_CompareWithASCIIString (attr_name, "components")) + && !PyUnicode_CompareWithASCIIString (attr_name, "components")) { uint8_t rgb[3]; color.get_rgb (rgb); @@ -135,25 +137,29 @@ get_attr (PyObject *obj, PyObject *attr_name) /* Implementation of Color.escape_sequence (self, is_fg) -> str. */ static PyObject * -colorpy_escape_sequence (PyObject *self, PyObject *is_fg_obj) +colorpy_escape_sequence (PyObject *self, PyObject *args, PyObject *kwargs) { - if (!gdbpy_is_color (self)) - { - PyErr_SetString (PyExc_RuntimeError, - _("Object is not gdb.Color.")); - return nullptr; - } + static const char *keywords[] = { "is_foreground", nullptr }; + PyObject *is_fg_obj; + + /* Parse method arguments. */ + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "O!", keywords, + &PyBool_Type, &is_fg_obj)) + return nullptr; - if (! PyBool_Check (is_fg_obj)) + /* Python ensures the type of SELF. */ + gdb_assert (gdbpy_is_color (self)); + + /* The argument parsing ensures we have a bool. */ + gdb_assert (PyBool_Check (is_fg_obj)); + + std::string s; + if (term_cli_styling ()) { - PyErr_SetString (PyExc_RuntimeError, - _("A boolean argument is required.")); - return nullptr; + bool is_fg = is_fg_obj == Py_True; + s = gdbpy_get_color (self).to_ansi (is_fg); } - bool is_fg = is_fg_obj == Py_True; - std::string s = gdbpy_get_color (self).to_ansi (is_fg); - return host_string_to_python_string (s.c_str ()).release (); } @@ -175,17 +181,20 @@ colorpy_init (PyObject *self, PyObject *args, PyObject *kwds) PyObject *colorspace_obj = nullptr; color_space colorspace = color_space::MONOCHROME; - if (! PyArg_ParseTuple (args, "|OO", &value_obj, &colorspace_obj)) + static const char *keywords[] = { "value", "color_space", nullptr }; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwds, "|OO", keywords, + &value_obj, &colorspace_obj)) return -1; try { - if (colorspace_obj) + if (colorspace_obj != nullptr) { if (PyLong_Check (colorspace_obj)) { long colorspace_id = -1; - if (! gdb_py_int_as_long (colorspace_obj, &colorspace_id)) + if (!gdb_py_int_as_long (colorspace_obj, &colorspace_id)) return -1; if (!color_space_safe_cast (&colorspace, colorspace_id)) error (_("colorspace %ld is out of range."), colorspace_id); @@ -201,11 +210,11 @@ colorpy_init (PyObject *self, PyObject *args, PyObject *kwds) else if (PyLong_Check (value_obj)) { long value = -1; - if (! gdb_py_int_as_long (value_obj, &value)) + if (!gdb_py_int_as_long (value_obj, &value)) return -1; if (value < 0 || value > INT_MAX) error (_("value %ld is out of range."), value); - if (colorspace_obj) + if (colorspace_obj != nullptr) obj->color = ui_file_style::color (colorspace, value); else obj->color = ui_file_style::color (value); @@ -256,7 +265,6 @@ colorpy_init (PyObject *self, PyObject *args, PyObject *kwds) return gdbpy_handle_gdb_exception (-1, except); } - Py_INCREF (self); return 0; } @@ -272,10 +280,10 @@ colorpy_str (PyObject *self) static int gdbpy_initialize_color (void) { - for (auto & pair : colorspace_constants) - if (PyModule_AddIntConstant (gdb_module, pair.name, - static_cast<long> (pair.value)) < 0) - return -1; + for (auto &pair : colorspace_constants) + if (PyModule_AddIntConstant (gdb_module, pair.name, + static_cast<long> (pair.value)) < 0) + return -1; colorpy_object_type.tp_new = PyType_GenericNew; return gdbpy_type_ready (&colorpy_object_type, gdb_module); @@ -285,7 +293,8 @@ gdbpy_initialize_color (void) static PyMethodDef color_methods[] = { - { "escape_sequence", colorpy_escape_sequence, METH_O, + { "escape_sequence", (PyCFunction) colorpy_escape_sequence, + METH_VARARGS | METH_KEYWORDS, "escape_sequence (is_foreground) -> str.\n\ Return the ANSI escape sequence for this color.\n\ IS_FOREGROUND indicates whether this is a foreground or background color."}, @@ -313,7 +322,7 @@ PyTypeObject colorpy_object_type = get_attr, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB color object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c index 93757ac..a6d9ad0 100644 --- a/gdb/python/py-connection.c +++ b/gdb/python/py-connection.c @@ -428,9 +428,7 @@ connpy_send_packet (PyObject *self, PyObject *args, PyObject *kw) /* Global initialization for this file. */ -void _initialize_py_connection (); -void -_initialize_py_connection () +INIT_GDB_FILE (py_connection) { gdb::observers::connection_removed.attach (connpy_connection_removed, "py-connection"); diff --git a/gdb/python/py-dap.c b/gdb/python/py-dap.c index 7196d6c..9a9875a 100644 --- a/gdb/python/py-dap.c +++ b/gdb/python/py-dap.c @@ -110,9 +110,7 @@ dap_interp::pre_command_loop () call_dap_fn ("pre_command_loop"); } -void _initialize_py_interp (); -void -_initialize_py_interp () +INIT_GDB_FILE (py_interp) { /* The dap code uses module typing, available starting python 3.5. */ #if PY_VERSION_HEX >= 0x03050000 diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c index 9ca8d22..17064dc 100644 --- a/gdb/python/py-disasm.c +++ b/gdb/python/py-disasm.c @@ -1311,12 +1311,13 @@ gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR memaddr, return {}; } - /* Check the result is a DisassemblerResult (or a sub-class). */ - if (!PyObject_IsInstance (result.get (), - (PyObject *) &disasm_result_object_type)) + /* Check the result is a DisassemblerResult. */ + if (!PyObject_TypeCheck (result.get (), &disasm_result_object_type)) { - PyErr_SetString (PyExc_TypeError, - _("Result is not a DisassemblerResult.")); + PyErr_Format + (PyExc_TypeError, + _("Result from Disassembler must be gdb.DisassemblerResult, not %s."), + Py_TYPE (result.get ())->tp_name); gdbpy_print_stack (); return std::optional<int> (-1); } diff --git a/gdb/python/py-gdb-readline.c b/gdb/python/py-gdb-readline.c index ea3a385..70ceebb 100644 --- a/gdb/python/py-gdb-readline.c +++ b/gdb/python/py-gdb-readline.c @@ -29,11 +29,7 @@ static char * gdbpy_readline_wrapper (FILE *sys_stdin, FILE *sys_stdout, -#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4 const char *prompt) -#else - char *prompt) -#endif { int n; const char *p = NULL; diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index e4f07c9..72f427f 100644 --- a/gdb/python/py-micmd.c +++ b/gdb/python/py-micmd.c @@ -578,9 +578,7 @@ PyTypeObject micmdpy_object_type = { 0, /* tp_alloc */ }; -void _initialize_py_micmd (); -void -_initialize_py_micmd () +INIT_GDB_FILE (py_micmd) { add_setshow_boolean_cmd ("py-micmd", class_maintenance, &pymicmd_debug, diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index 763680e..06237b6 100644 --- a/gdb/python/py-param.c +++ b/gdb/python/py-param.c @@ -495,7 +495,11 @@ get_doc_string (PyObject *object, enum doc_string_type doc_type, } } - if (result == nullptr) + /* For the set/show docs, if these strings are empty then we set then to + a non-empty string. This ensures that the command has some sane + documentation for its 'help' text. */ + if (result == nullptr + || (doc_type != doc_string_description && *result == '\0')) { if (doc_type == doc_string_description) result.reset (xstrdup (_("This command is not documented."))); @@ -904,6 +908,18 @@ parmpy_init (PyObject *self, PyObject *args, PyObject *kwds) show_doc = get_doc_string (self, doc_string_show, name); doc = get_doc_string (self, doc_string_description, cmd_name.get ()); + /* The set/show docs should always be a non-empty string. */ + gdb_assert (set_doc != nullptr && *set_doc != '\0'); + gdb_assert (show_doc != nullptr && *show_doc != '\0'); + + /* For the DOC string only, if it is the empty string, then we convert it + to NULL. This means GDB will not even display a blank line for this + part of the help text, instead the set/show line is all the user will + get. */ + gdb_assert (doc != nullptr); + if (*doc == '\0') + doc = nullptr; + Py_INCREF (self); try diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c index 78a806c..9be2e3c 100644 --- a/gdb/python/py-registers.c +++ b/gdb/python/py-registers.c @@ -403,8 +403,7 @@ gdbpy_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id, PyErr_SetString (PyExc_ValueError, "Bad register"); } /* The register could be a gdb.RegisterDescriptor object. */ - else if (PyObject_IsInstance (pyo_reg_id, - (PyObject *) ®ister_descriptor_object_type)) + else if (PyObject_TypeCheck (pyo_reg_id, ®ister_descriptor_object_type)) { register_descriptor_object *reg = (register_descriptor_object *) pyo_reg_id; diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c index f37afde..c546aa7 100644 --- a/gdb/python/py-type.c +++ b/gdb/python/py-type.c @@ -158,7 +158,7 @@ convert_field (struct type *type, int field) } else { - if (type->field (field).loc_kind () == FIELD_LOC_KIND_DWARF_BLOCK) + if (type->field (field).loc_is_dwarf_block ()) arg = gdbpy_ref<>::new_reference (Py_None); else arg = gdb_py_object_from_longest (type->field (field).loc_bitpos ()); diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c index ab34971..dc078ec 100644 --- a/gdb/python/py-unwind.c +++ b/gdb/python/py-unwind.c @@ -929,9 +929,9 @@ frame_unwind_python::sniff (const frame_info_ptr &this_frame, /* Received UnwindInfo, cache data. */ PyObject *pyo_unwind_info = PyTuple_GET_ITEM (pyo_execute_ret.get (), 0); - if (PyObject_IsInstance (pyo_unwind_info, - (PyObject *) &unwind_info_object_type) <= 0) - error (_("A Unwinder should return gdb.UnwindInfo instance.")); + if (!PyObject_TypeCheck (pyo_unwind_info, &unwind_info_object_type)) + error (_("an Unwinder should return gdb.UnwindInfo, not %s."), + Py_TYPE (pyo_unwind_info)->tp_name); { unwind_info_object *unwind_info = @@ -1029,9 +1029,7 @@ gdbpy_initialize_unwind (void) return 0; } -void _initialize_py_unwind (); -void -_initialize_py_unwind () +INIT_GDB_FILE (py_unwind) { add_setshow_boolean_cmd ("py-unwind", class_maintenance, &pyuw_debug, diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 5262d76..7f4237e 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -88,6 +88,8 @@ #include <frameobject.h> #include "py-ref.h" +static_assert (PY_VERSION_HEX >= 0x03040000); + #define Py_TPFLAGS_CHECKTYPES 0 /* If Python.h does not define WITH_THREAD, then the various @@ -135,17 +137,6 @@ typedef unsigned long gdb_py_ulongest; #endif /* HAVE_LONG_LONG */ -#if PY_VERSION_HEX < 0x03020000 -typedef long Py_hash_t; -#endif - -/* PyMem_RawMalloc appeared in Python 3.4. For earlier versions, we can just - fall back to PyMem_Malloc. */ - -#if PY_VERSION_HEX < 0x03040000 -#define PyMem_RawMalloc PyMem_Malloc -#endif - /* A template variable holding the format character (as for Py_BuildValue) for a given type. */ template<typename T> @@ -519,7 +510,8 @@ PyObject *gdbpy_string_to_argv (PyObject *self, PyObject *args); PyObject *gdbpy_parameter_value (const setting &var); gdb::unique_xmalloc_ptr<char> gdbpy_parse_command_name (const char *name, struct cmd_list_element ***base_list, - struct cmd_list_element **start_list); + struct cmd_list_element **start_list, + struct cmd_list_element **prefix_cmd = nullptr); PyObject *gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw); diff --git a/gdb/python/python.c b/gdb/python/python.c index 7e28eb6..cb0d642 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1612,16 +1612,53 @@ gdbpy_flush (PyObject *self, PyObject *args, PyObject *kw) { case 1: { - gdb_flush (gdb_stderr); + if (gdb_stderr != nullptr) + gdb_flush (gdb_stderr); break; } case 2: { - gdb_flush (gdb_stdlog); + if (gdb_stdlog != nullptr) + gdb_flush (gdb_stdlog); break; } default: - gdb_flush (gdb_stdout); + if (gdb_stdout != nullptr) + gdb_flush (gdb_stdout); + } + + Py_RETURN_NONE; +} + +/* Implement gdb.warning(). Takes a single text string argument and emit a + warning using GDB's 'warning' function. The input text string must not + be empty. */ + +static PyObject * +gdbpy_warning (PyObject *self, PyObject *args, PyObject *kw) +{ + const char *text; + static const char *keywords[] = { "text", nullptr }; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s", keywords, &text)) + return nullptr; + + if (strlen (text) == 0) + { + PyErr_SetString (PyExc_ValueError, + _("Empty text string passed to gdb.warning")); + return nullptr; + } + + try + { + warning ("%s", text); + } + catch (const gdb_exception &ex) + { + /* The warning() call probably cannot throw an exception. But just + in case it ever does. */ + return gdbpy_handle_gdb_exception (nullptr, ex); } Py_RETURN_NONE; @@ -2443,7 +2480,7 @@ py_initialize () /foo/lib/pythonX.Y/... This must be done before calling Py_Initialize. */ gdb::unique_xmalloc_ptr<char> progname - (concat (ldirname (python_libdir.c_str ()).c_str (), SLASH_STRING, "bin", + (concat (gdb_ldirname (python_libdir.c_str ()).c_str (), SLASH_STRING, "bin", SLASH_STRING, "python", (char *) NULL)); { @@ -2680,9 +2717,7 @@ test_python () /* See python.h. */ cmd_list_element *python_cmd_element = nullptr; -void _initialize_python (); -void -_initialize_python () +INIT_GDB_FILE (python) { cmd_list_element *python_interactive_cmd = add_com ("python-interactive", class_obscure, @@ -3121,6 +3156,12 @@ Return the current print options." }, METH_VARARGS | METH_KEYWORDS, "notify_mi (name, data) -> None\n\ Output async record to MI channels if any." }, + + { "warning", (PyCFunction) gdbpy_warning, + METH_VARARGS | METH_KEYWORDS, + "warning (text) -> None\n\ +Print a warning." }, + {NULL, NULL, 0, NULL} }; |