diff options
Diffstat (limited to 'gdb/python')
118 files changed, 1228 insertions, 787 deletions
diff --git a/gdb/python/lib/gdb/FrameDecorator.py b/gdb/python/lib/gdb/FrameDecorator.py index 5cdfbe1..fa6effa 100644 --- a/gdb/python/lib/gdb/FrameDecorator.py +++ b/gdb/python/lib/gdb/FrameDecorator.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2024 Free Software Foundation, Inc. +# Copyright (C) 2013-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -174,7 +174,7 @@ class FrameDecorator(_FrameDecoratorBase): sub-classed from FrameDecorator. If Decorator1 just overrides the 'function' method, then all of the other methods are carried out by the super-class FrameDecorator. But Decorator2 may have - overriden other methods, so FrameDecorator will look at the + overridden other methods, so FrameDecorator will look at the 'base' parameter and defer to that class's methods. And so on, down the chain.""" diff --git a/gdb/python/lib/gdb/FrameIterator.py b/gdb/python/lib/gdb/FrameIterator.py index 75176c3..54534fe 100644 --- a/gdb/python/lib/gdb/FrameIterator.py +++ b/gdb/python/lib/gdb/FrameIterator.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2024 Free Software Foundation, Inc. +# Copyright (C) 2013-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 146a963..cedd897 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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/command/__init__.py b/gdb/python/lib/gdb/command/__init__.py index f1b13bd..3688152 100644 --- a/gdb/python/lib/gdb/command/__init__.py +++ b/gdb/python/lib/gdb/command/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/explore.py b/gdb/python/lib/gdb/command/explore.py index e359fa5..6107338 100644 --- a/gdb/python/lib/gdb/command/explore.py +++ b/gdb/python/lib/gdb/command/explore.py @@ -1,5 +1,5 @@ # GDB 'explore' command. -# Copyright (C) 2012-2024 Free Software Foundation, Inc. +# Copyright (C) 2012-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/frame_filters.py b/gdb/python/lib/gdb/command/frame_filters.py index 4e1b320..be7be9a 100644 --- a/gdb/python/lib/gdb/command/frame_filters.py +++ b/gdb/python/lib/gdb/command/frame_filters.py @@ -1,5 +1,5 @@ # Frame-filter commands. -# Copyright (C) 2013-2024 Free Software Foundation, Inc. +# Copyright (C) 2013-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/missing_files.py b/gdb/python/lib/gdb/command/missing_files.py index 463853b..09d9684 100644 --- a/gdb/python/lib/gdb/command/missing_files.py +++ b/gdb/python/lib/gdb/command/missing_files.py @@ -1,6 +1,6 @@ # Missing debug and objfile related commands. # -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/pretty_printers.py b/gdb/python/lib/gdb/command/pretty_printers.py index cb9b9f3..f62d329 100644 --- a/gdb/python/lib/gdb/command/pretty_printers.py +++ b/gdb/python/lib/gdb/command/pretty_printers.py @@ -1,5 +1,5 @@ # Pretty-printer commands. -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/prompt.py b/gdb/python/lib/gdb/command/prompt.py index 2cfb25d..6574c83 100644 --- a/gdb/python/lib/gdb/command/prompt.py +++ b/gdb/python/lib/gdb/command/prompt.py @@ -1,5 +1,5 @@ # Extended prompt. -# Copyright (C) 2011-2024 Free Software Foundation, Inc. +# Copyright (C) 2011-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/type_printers.py b/gdb/python/lib/gdb/command/type_printers.py index a2be226..9fc654c 100644 --- a/gdb/python/lib/gdb/command/type_printers.py +++ b/gdb/python/lib/gdb/command/type_printers.py @@ -1,5 +1,5 @@ # Type printer commands. -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py index b863b33..ffedab8 100644 --- a/gdb/python/lib/gdb/command/unwinders.py +++ b/gdb/python/lib/gdb/command/unwinders.py @@ -1,5 +1,5 @@ # Unwinder commands. -# Copyright 2015-2024 Free Software Foundation, Inc. +# Copyright 2015-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/command/xmethods.py b/gdb/python/lib/gdb/command/xmethods.py index f786227..719c146 100644 --- a/gdb/python/lib/gdb/command/xmethods.py +++ b/gdb/python/lib/gdb/command/xmethods.py @@ -1,5 +1,5 @@ # Xmethod commands. -# Copyright 2013-2024 Free Software Foundation, Inc. +# Copyright 2013-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -37,7 +37,7 @@ def parse_xm_command_args(arg): Returns: A 3-tuple: (<locus matching regular expression>, <matcher matching regular expression>, - <name matching regular experession>) + <name matching regular expression>) """ argv = gdb.string_to_argv(arg) argc = len(argv) diff --git a/gdb/python/lib/gdb/dap/__init__.py b/gdb/python/lib/gdb/dap/__init__.py index 145aeb6..1c3cf8e 100644 --- a/gdb/python/lib/gdb/dap/__init__.py +++ b/gdb/python/lib/gdb/dap/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ from . import startup # server object. "F401" is the flake8 "imported but unused" code. from . import breakpoint # noqa: F401 from . import bt # noqa: F401 +from . import completions # noqa: F401 from . import disassemble # noqa: F401 from . import evaluate # noqa: F401 from . import launch # noqa: F401 @@ -95,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 f0fe073..4d4ca18 100644 --- a/gdb/python/lib/gdb/dap/breakpoint.py +++ b/gdb/python/lib/gdb/dap/breakpoint.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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] @@ -218,11 +214,11 @@ class _PrintBreakpoint(gdb.Breakpoint): def __init__(self, logMessage, **args): super().__init__(**args) # Split the message up for easier processing. - self.message = re.split("{(.*?)}", logMessage) + self._message = re.split("{(.*?)}", logMessage) def stop(self): output = "" - for idx, item in enumerate(self.message): + for idx, item in enumerate(self._message): if idx % 2 == 0: # Even indices are plain text. output += item @@ -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/bt.py b/gdb/python/lib/gdb/dap/bt.py index 0fefa69..41c7d00 100644 --- a/gdb/python/lib/gdb/dap/bt.py +++ b/gdb/python/lib/gdb/dap/bt.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/completions.py b/gdb/python/lib/gdb/dap/completions.py new file mode 100644 index 0000000..e5003ff --- /dev/null +++ b/gdb/python/lib/gdb/dap/completions.py @@ -0,0 +1,63 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from typing import Optional + +from .frames import select_frame +from .server import capability, import_column, import_line, request +from .startup import exec_mi_and_log + + +@request("completions") +@capability("supportsCompletionsRequest") +@capability("completionTriggerCharacters", [" ", "."]) +def completions( + *, + frameId: Optional[int] = None, + text: str, + column: int, + line: Optional[int] = None, + **extra, +): + if frameId is not None: + select_frame(frameId) + + column = import_column(column) + if line is None: + line = 1 + else: + line = import_line(line) + if text: + text = text.splitlines()[line - 1] + text = text[: column - 1] + else: + text = "" + mi_result = exec_mi_and_log("-complete", text) + result = [] + completion = None + if "completion" in mi_result: + completion = mi_result["completion"] + result.append({"label": completion, "length": len(completion)}) + # If `-complete' finds one match then `completion' and `matches' + # will contain the same one match. + if ( + completion is not None + and len(mi_result["matches"]) == 1 + and completion == mi_result["matches"][0] + ): + return {"targets": result} + for match in mi_result["matches"]: + result.append({"label": match, "length": len(match)}) + return {"targets": result} diff --git a/gdb/python/lib/gdb/dap/disassemble.py b/gdb/python/lib/gdb/dap/disassemble.py index 5389803..42cad3e 100644 --- a/gdb/python/lib/gdb/dap/disassemble.py +++ b/gdb/python/lib/gdb/dap/disassemble.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,30 +26,30 @@ class _BlockTracker: # Map from PC to symbol names. A given PC is assumed to have # just one label -- DAP wouldn't let us return multiple labels # anyway. - self.labels = {} + self._labels = {} # Blocks that have already been handled. - self.blocks = set() + self._blocks = set() # Add a gdb.Block and its superblocks, ignoring the static and # global block. BLOCK can also be None, which is ignored. def add_block(self, block): while block is not None: - if block.is_static or block.is_global or block in self.blocks: + if block.is_static or block.is_global or block in self._blocks: return - self.blocks.add(block) + self._blocks.add(block) if block.function is not None: - self.labels[block.start] = block.function.name + self._labels[block.start] = block.function.name for sym in block: if sym.addr_class == gdb.SYMBOL_LOC_LABEL: - self.labels[int(sym.value())] = sym.name + self._labels[int(sym.value())] = sym.name block = block.superblock # Add PC to this tracker. Update RESULT as appropriate with # information about the source and any label. def add_pc(self, pc, result): self.add_block(gdb.block_for_pc(pc)) - if pc in self.labels: - result["symbol"] = self.labels[pc] + if pc in self._labels: + result["symbol"] = self._labels[pc] sal = gdb.find_pc_line(pc) if sal.symtab is not None: if sal.line != 0: diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py index 34843f4..fcbcc99 100644 --- a/gdb/python/lib/gdb/dap/evaluate.py +++ b/gdb/python/lib/gdb/dap/evaluate.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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 2e6fe98..e8f2655 100644 --- a/gdb/python/lib/gdb/dap/events.py +++ b/gdb/python/lib/gdb/dap/events.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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 f4e6565..4dacb87 100644 --- a/gdb/python/lib/gdb/dap/frames.py +++ b/gdb/python/lib/gdb/dap/frames.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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] @@ -75,16 +74,16 @@ def select_frame(id): # what is needed for the current callers. class _MemoizingIterator: def __init__(self, iterator): - self.iterator = iterator - self.seen = [] + self._iterator = iterator + self._seen = [] def __iter__(self): # First the memoized items. - for item in self.seen: + for item in self._seen: yield item # Now memoize new items. - for item in self.iterator: - self.seen.append(item) + for item in self._iterator: + self._seen.append(item) yield item @@ -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 104b242..9d64d28 100644 --- a/gdb/python/lib/gdb/dap/globalvars.py +++ b/gdb/python/lib/gdb/dap/globalvars.py @@ -1,4 +1,4 @@ -# Copyright 2024 Free Software Foundation, Inc. +# Copyright 2024-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -37,8 +37,8 @@ gdb.events.cont.connect(clear) class _Globals(BaseReference): def __init__(self, filename, var_list): super().__init__("Globals") - self.filename = filename - self.var_list = var_list + self._filename = filename + self._var_list = var_list def to_object(self): result = super().to_object() @@ -46,8 +46,8 @@ class _Globals(BaseReference): # How would we know? result["expensive"] = False result["namedVariables"] = self.child_count() - if self.filename is not None: - result["source"] = make_source(self.filename) + if self._filename is not None: + result["source"] = make_source(self._filename) return result def has_children(self): @@ -56,11 +56,11 @@ class _Globals(BaseReference): return True def child_count(self): - return len(self.var_list) + return len(self._var_list) @in_gdb_thread def fetch_one_child(self, idx): - sym = self.var_list[idx] + sym = self._var_list[idx] return (sym.name, sym.value()) @@ -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/io.py b/gdb/python/lib/gdb/dap/io.py index 03031a7..45890da 100644 --- a/gdb/python/lib/gdb/dap/io.py +++ b/gdb/python/lib/gdb/dap/io.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py index fc1890c..8ac4c77 100644 --- a/gdb/python/lib/gdb/dap/launch.py +++ b/gdb/python/lib/gdb/dap/launch.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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/locations.py b/gdb/python/lib/gdb/dap/locations.py index 1ef5a34..fffc038 100644 --- a/gdb/python/lib/gdb/dap/locations.py +++ b/gdb/python/lib/gdb/dap/locations.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/memory.py b/gdb/python/lib/gdb/dap/memory.py index 4aa4996..d0f8825 100644 --- a/gdb/python/lib/gdb/dap/memory.py +++ b/gdb/python/lib/gdb/dap/memory.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/modules.py b/gdb/python/lib/gdb/dap/modules.py index 69e5a40..b06f771 100644 --- a/gdb/python/lib/gdb/dap/modules.py +++ b/gdb/python/lib/gdb/dap/modules.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py index 7e06b1b..898fff1 100644 --- a/gdb/python/lib/gdb/dap/next.py +++ b/gdb/python/lib/gdb/dap/next.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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/pause.py b/gdb/python/lib/gdb/dap/pause.py index d874a60..c254e45 100644 --- a/gdb/python/lib/gdb/dap/pause.py +++ b/gdb/python/lib/gdb/dap/pause.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py index 221ae35..7ce3a7f 100644 --- a/gdb/python/lib/gdb/dap/scopes.py +++ b/gdb/python/lib/gdb/dap/scopes.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ from .sources import make_source from .startup import in_gdb_thread from .varref import BaseReference -# Map DAP frame IDs to scopes. This ensures that scopes are re-used. +# Map DAP frame IDs to scopes. This ensures that scopes are reused. frame_to_scope = {} @@ -78,19 +78,19 @@ def symbol_value(sym, frame): class _ScopeReference(BaseReference): def __init__(self, name, hint, frameId: int, var_list): super().__init__(name) - self.hint = hint - self.frameId = frameId + self._hint = hint + self._frameId = frameId # VAR_LIST might be any kind of iterator, but it's convenient # here if it is just a collection. - self.var_list = tuple(var_list) + self._var_list = tuple(var_list) def to_object(self): result = super().to_object() - result["presentationHint"] = self.hint + result["presentationHint"] = self._hint # How would we know? result["expensive"] = False result["namedVariables"] = self.child_count() - frame = frame_for_id(self.frameId) + frame = frame_for_id(self._frameId) if frame.line() is not None: result["line"] = export_line(frame.line()) filename = frame.filename() @@ -102,11 +102,11 @@ class _ScopeReference(BaseReference): return True def child_count(self): - return len(self.var_list) + return len(self._var_list) @in_gdb_thread def fetch_one_child(self, idx): - return symbol_value(self.var_list[idx], frame_for_id(self.frameId)) + return symbol_value(self._var_list[idx], frame_for_id(self._frameId)) # A _ScopeReference that wraps the 'finish' value. Note that this @@ -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) @@ -136,17 +135,15 @@ class _RegisterReference(_ScopeReference): @in_gdb_thread def fetch_one_child(self, idx): return ( - self.var_list[idx].name, - frame_for_id(self.frameId) + self._var_list[idx].name, + frame_for_id(self._frameId) .inferior_frame() - .read_register(self.var_list[idx]), + .read_register(self._var_list[idx]), ) @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 6f3af73..7dab582 100644 --- a/gdb/python/lib/gdb/dap/server.py +++ b/gdb/python/lib/gdb/dap/server.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -49,6 +49,7 @@ _server = None # This is set by the initialize request and is used when rewriting # line numbers. _lines_start_at_1 = False +_columns_start_at_1 = False class DeferredRequest: @@ -73,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. @@ -94,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 @@ -115,10 +126,10 @@ class CancellationHandler: # The request currently being handled, or None. self.in_flight_dap_thread = None self.in_flight_gdb_thread = None - self.reqs = [] + self._reqs = [] # A set holding the request IDs of all deferred requests that # are still unresolved. - self.deferred_ids = set() + self._deferred_ids = set() @contextmanager def current_request(self, req): @@ -138,7 +149,7 @@ class CancellationHandler: def defer_request(self, req): """Indicate that the request REQ has been deferred.""" with self.lock: - self.deferred_ids.add(req) + self._deferred_ids.add(req) def request_finished(self, req): """Indicate that the request REQ is finished. @@ -150,7 +161,7 @@ class CancellationHandler: with self.lock: # Use discard here, not remove, because this is called # regardless of whether REQ was deferred. - self.deferred_ids.discard(req) + self._deferred_ids.discard(req) def check_cancel(self, req): """Check whether request REQ is cancelled. @@ -163,15 +174,15 @@ class CancellationHandler: deferred = [] try: # If the request is cancelled, don't execute the region. - while len(self.reqs) > 0 and self.reqs[0] <= req: + while len(self._reqs) > 0 and self._reqs[0] <= req: # In most cases, if we see a cancellation request # on the heap that is before REQ, we can just # ignore it -- we missed our chance to cancel that # request. - next_id = heapq.heappop(self.reqs) + next_id = heapq.heappop(self._reqs) if next_id == req: raise KeyboardInterrupt() - elif next_id in self.deferred_ids: + elif next_id in self._deferred_ids: # We could be in a situation where we're # processing request 23, but request 18 is # still deferred. In this case, popping @@ -180,7 +191,7 @@ class CancellationHandler: deferred.append(next_id) finally: for x in deferred: - heapq.heappush(self.reqs, x) + heapq.heappush(self._reqs, x) def cancel(self, req): """Call to cancel a request. @@ -198,7 +209,7 @@ class CancellationHandler: # the weird property that a request can be cancelled # before it is even sent. It didn't seem worthwhile # to try to check for this. - heapq.heappush(self.reqs, req) + heapq.heappush(self._reqs, req) @contextmanager def interruptable_region(self, req): @@ -225,20 +236,20 @@ class Server: """The DAP server class.""" def __init__(self, in_stream, out_stream, child_stream): - self.in_stream = in_stream - self.out_stream = out_stream - self.child_stream = child_stream - self.delayed_fns_lock = threading.Lock() - self.defer_stop_events = False - self.delayed_fns = [] + self._in_stream = in_stream + self._out_stream = out_stream + self._child_stream = child_stream + self._delayed_fns_lock = threading.Lock() + 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 # blocking the read loop. - self.write_queue = DAPQueue() + self._write_queue = DAPQueue() # Reading is also done in a separate thread, and a queue of # requests is kept. - self.read_queue = DAPQueue() - self.done = False + self._read_queue = DAPQueue() + self._done = False self.canceller = CancellationHandler() global _server _server = self @@ -306,7 +317,6 @@ class Server: args = {} def fn(): - global _commands return _commands[params["command"]](**args) self.invoke_request(req, result, fn) @@ -315,8 +325,8 @@ class Server: # is run in its own thread. def _read_inferior_output(self): while True: - line = self.child_stream.readline() - self.send_event( + line = self._child_stream.readline() + self.send_event_maybe_later( "output", { "category": "stdout", @@ -327,7 +337,7 @@ class Server: # Send OBJ to the client, logging first if needed. def _send_json(self, obj): log("WROTE: <<<" + json.dumps(obj) + ">>>") - self.write_queue.put(obj) + self._write_queue.put(obj) # This is run in a separate thread and simply reads requests from # the client and puts them into a queue. A separate thread is @@ -335,7 +345,7 @@ class Server: # will normally block, waiting for each request to complete. def _reader_thread(self): while True: - cmd = read_json(self.in_stream) + cmd = read_json(self._in_stream) if cmd is None: break log("READ: <<<" + json.dumps(cmd) + ">>>") @@ -351,9 +361,20 @@ class Server: and "requestId" in cmd["arguments"] ): self.canceller.cancel(cmd["arguments"]["requestId"]) - self.read_queue.put(cmd) + self._read_queue.put(cmd) # When we hit EOF, signal it with None. - self.read_queue.put(None) + 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): @@ -361,38 +382,32 @@ class Server: # Before looping, start the thread that writes JSON to the # client, and the thread that reads output from the inferior. start_thread("output reader", self._read_inferior_output) - json_writer = start_json_writer(self.out_stream, self.write_queue) + json_writer = start_json_writer(self._out_stream, self._write_queue) start_thread("JSON reader", self._reader_thread) - while not self.done: - cmd = self.read_queue.get() + while not self._done: + cmd = self._read_queue.get() # A None value here means the reader hit EOF. if cmd is None: break 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. - self.write_queue.put(None) + self._write_queue.put(None) json_writer.join() 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.""" - with self.delayed_fns_lock: - self.delayed_fns.append(lambda: self.send_event(event, body)) + def set_defer_events(self): + """Defer any events until the current request has completed.""" + with self._delayed_fns_lock: + 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, @@ -400,22 +415,22 @@ class Server: the client.""" 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)) + with self._delayed_fns_lock: + 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): """Call FN later -- after the current request's response has been sent.""" - with self.delayed_fns_lock: - self.delayed_fns.append(fn) + with self._delayed_fns_lock: + self._delayed_fns.append(fn) # 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.""" @@ -432,29 +447,18 @@ class Server: # Just set a flag. This operation is complicated because we # want to write the result of the request before exiting. See # main_loop. - self.done = True + self._done = True 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) @@ -479,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. @@ -501,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. @@ -526,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 @@ -554,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 @@ -567,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 @@ -580,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 @@ -588,11 +595,12 @@ 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 + _columns_start_at_1 = client_bool_capability("columnsStartAt1", True) return _capabilities.copy() @@ -629,19 +637,19 @@ class Invoker(object): """A simple class that can invoke a gdb command.""" def __init__(self, cmd): - self.cmd = cmd + self._cmd = cmd # This is invoked in the gdb thread to run the command. @in_gdb_thread def __call__(self): - exec_and_log(self.cmd) + exec_and_log(self._cmd) class Cancellable(object): def __init__(self, fn, result_q=None): - self.fn = fn - self.result_q = result_q + self._fn = fn + self._result_q = result_q with _server.canceller.lock: self.req = _server.canceller.in_flight_dap_thread @@ -650,13 +658,13 @@ class Cancellable(object): def __call__(self): try: with _server.canceller.interruptable_region(self.req): - val = self.fn() - if self.result_q is not None: - self.result_q.put(val) + val = self._fn() + if self._result_q is not None: + self._result_q.put(val) except (Exception, KeyboardInterrupt) as e: - if self.result_q is not None: + if self._result_q is not None: # Pass result or exception to caller. - self.result_q.put(e) + self._result_q.put(e) elif isinstance(e, KeyboardInterrupt): # Fn was cancelled. pass @@ -698,11 +706,10 @@ def send_gdb_with_response(fn): return val -def export_line(line): +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. @@ -710,13 +717,26 @@ def export_line(line): return line -def import_line(line): +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. line = line + 1 return line + + +def export_column(column: int) -> int: + """Rewrite COLUMN according to client capability. + This applies the columnsStartAt1 capability as needed, + when sending a column number from gdb to the client.""" + return column if _columns_start_at_1 else column - 1 + + +def import_column(column: int) -> int: + """Rewrite COLUMN according to client capability. + This applies the columnsStartAt1 capability as needed, + when the client sends a column number to gdb.""" + return column if _columns_start_at_1 else column + 1 diff --git a/gdb/python/lib/gdb/dap/sources.py b/gdb/python/lib/gdb/dap/sources.py index a9f4ea6..efcd799 100644 --- a/gdb/python/lib/gdb/dap/sources.py +++ b/gdb/python/lib/gdb/dap/sources.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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/startup.py b/gdb/python/lib/gdb/dap/startup.py index a3f048b..ab3e8fd 100644 --- a/gdb/python/lib/gdb/dap/startup.py +++ b/gdb/python/lib/gdb/dap/startup.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/state.py b/gdb/python/lib/gdb/dap/state.py index 57ae355..5fdfbb2 100644 --- a/gdb/python/lib/gdb/dap/state.py +++ b/gdb/python/lib/gdb/dap/state.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/threads.py b/gdb/python/lib/gdb/dap/threads.py index e65495b..89046a8 100644 --- a/gdb/python/lib/gdb/dap/threads.py +++ b/gdb/python/lib/gdb/dap/threads.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright 2022-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -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/typecheck.py b/gdb/python/lib/gdb/dap/typecheck.py index 55896cc..1496b67 100644 --- a/gdb/python/lib/gdb/dap/typecheck.py +++ b/gdb/python/lib/gdb/dap/typecheck.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py index 0dd9879..8a13c51 100644 --- a/gdb/python/lib/gdb/dap/varref.py +++ b/gdb/python/lib/gdb/dap/varref.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 Free Software Foundation, Inc. +# Copyright 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -60,8 +60,6 @@ class BaseReference(ABC): This class is just a base class, some methods must be implemented in subclasses. - - The 'ref' field can be used as the variablesReference in the protocol. """ @in_gdb_thread @@ -71,10 +69,9 @@ 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 + self._ref = len(all_variables) + self._name = name self.reset_children() @in_gdb_thread @@ -83,9 +80,9 @@ class BaseReference(ABC): The resulting object is a starting point that can be filled in further. See the Scope or Variable types in the spec""" - result = {"variablesReference": self.ref if self.has_children() else 0} - if self.name is not None: - result["name"] = str(self.name) + result = {"variablesReference": self._ref if self.has_children() else 0} + if self._name is not None: + result["name"] = str(self._name) return result @abstractmethod @@ -97,13 +94,13 @@ class BaseReference(ABC): """Reset any cached information about the children of this object.""" # A list of all the children. Each child is a BaseReference # of some kind. - self.children = None + self._children = None # Map from the name of a child to a BaseReference. - self.by_name = {} + self._by_name = {} # Keep track of how many duplicates there are of a given name, # so that unique names can be generated. Map from base name # to a count. - self.name_counts = defaultdict(lambda: 1) + self._name_counts = defaultdict(lambda: 1) @abstractmethod def fetch_one_child(self, index): @@ -128,13 +125,13 @@ class BaseReference(ABC): # and # https://github.com/microsoft/debug-adapter-protocol/issues/149 def _compute_name(self, name): - if name in self.by_name: - self.name_counts[name] += 1 + if name in self._by_name: + self._name_counts[name] += 1 # In theory there's no safe way to compute a name, because # a pretty-printer might already be generating names of # that form. In practice I think we should not worry too # much. - name = name + " #" + str(self.name_counts[name]) + name = name + " #" + str(self._name_counts[name]) return name @in_gdb_thread @@ -146,16 +143,16 @@ class BaseReference(ABC): Returns an iterable of some kind.""" if count == 0: count = self.child_count() - if self.children is None: - self.children = [None] * self.child_count() + if self._children is None: + self._children = [None] * self.child_count() for idx in range(start, start + count): - if self.children[idx] is None: + if self._children[idx] is None: (name, value) = self.fetch_one_child(idx) name = self._compute_name(name) var = VariableReference(name, value) - self.children[idx] = var - self.by_name[name] = var - yield self.children[idx] + self._children[idx] = var + self._by_name[name] = var + yield self._children[idx] @in_gdb_thread def find_child_by_name(self, name): @@ -165,8 +162,8 @@ class BaseReference(ABC): # A lookup by name can only be done using names previously # provided to the client, so we can simply rely on the by-name # map here. - if name in self.by_name: - return self.by_name[name] + if name in self._by_name: + return self._by_name[name] raise DAPException("no variable named '" + name + "'") @@ -181,15 +178,15 @@ class VariableReference(BaseReference): RESULT_NAME can be used to change how the simple string result is emitted in the result dictionary.""" super().__init__(name) - self.result_name = result_name - self.value = value + self._result_name = result_name + self._value = value self._update_value() # Internal method to update local data when the value changes. def _update_value(self): self.reset_children() - self.printer = gdb.printing.make_visualizer(self.value) - self.child_cache = None + self._printer = gdb.printing.make_visualizer(self._value) + self._child_cache = None if self.has_children(): self.count = -1 else: @@ -197,32 +194,32 @@ class VariableReference(BaseReference): def assign(self, value): """Assign VALUE to this object and update.""" - self.value.assign(value) + self._value.assign(value) self._update_value() def has_children(self): - return hasattr(self.printer, "children") + return hasattr(self._printer, "children") def cache_children(self): - if self.child_cache is None: + if self._child_cache is None: # This discards all laziness. This could be improved # slightly by lazily evaluating children, but because this # code also generally needs to know the number of # children, it probably wouldn't help much. Note that # this is only needed with legacy (non-ValuePrinter) # printers. - self.child_cache = list(self.printer.children()) - return self.child_cache + self._child_cache = list(self._printer.children()) + return self._child_cache def child_count(self): if self.count is None: return None if self.count == -1: num_children = None - if isinstance(self.printer, gdb.ValuePrinter) and hasattr( - self.printer, "num_children" + if isinstance(self._printer, gdb.ValuePrinter) and hasattr( + self._printer, "num_children" ): - num_children = self.printer.num_children() + num_children = self._printer.num_children() if num_children is None: num_children = len(self.cache_children()) self.count = num_children @@ -230,12 +227,12 @@ class VariableReference(BaseReference): def to_object(self): result = super().to_object() - result[self.result_name] = str(self.printer.to_string()) + result[self._result_name] = str(self._printer.to_string()) num_children = self.child_count() if num_children is not None: if ( - hasattr(self.printer, "display_hint") - and self.printer.display_hint() == "array" + hasattr(self._printer, "display_hint") + and self._printer.display_hint() == "array" ): result["indexedVariables"] = num_children else: @@ -245,18 +242,18 @@ class VariableReference(BaseReference): # changed DAP to allow memory references for any of the # variable response requests, and to lift the restriction # to pointer-to-function from Variable. - if self.value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR: - result["memoryReference"] = hex(int(self.value)) + if self._value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR: + result["memoryReference"] = hex(int(self._value)) if client_bool_capability("supportsVariableType"): - result["type"] = str(self.value.type) + result["type"] = str(self._value.type) return result @in_gdb_thread def fetch_one_child(self, idx): - if isinstance(self.printer, gdb.ValuePrinter) and hasattr( - self.printer, "child" + if isinstance(self._printer, gdb.ValuePrinter) and hasattr( + self._printer, "child" ): - (name, val) = self.printer.child(idx) + (name, val) = self._printer.child(idx) else: (name, val) = self.cache_children()[idx] # A pretty-printer can return something other than a @@ -269,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/disassembler.py b/gdb/python/lib/gdb/disassembler.py index 7d0e781..8f8e768 100644 --- a/gdb/python/lib/gdb/disassembler.py +++ b/gdb/python/lib/gdb/disassembler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021-2024 Free Software Foundation, Inc. +# Copyright (C) 2021-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -81,7 +81,7 @@ def register_disassembler(disassembler, architecture=None): # Call the private _set_enabled function within the # _gdb.disassembler module. This function sets a global flag - # within GDB's C++ code that enables or dissables the Python + # within GDB's C++ code that enables or disables the Python # disassembler functionality, this improves performance of the # disassembler by avoiding unneeded calls into Python when we know # that no disassemblers are registered. diff --git a/gdb/python/lib/gdb/frames.py b/gdb/python/lib/gdb/frames.py index a3be80c7..96174e9 100644 --- a/gdb/python/lib/gdb/frames.py +++ b/gdb/python/lib/gdb/frames.py @@ -1,5 +1,5 @@ # Frame-filter commands. -# Copyright (C) 2013-2024 Free Software Foundation, Inc. +# Copyright (C) 2013-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/function/__init__.py b/gdb/python/lib/gdb/function/__init__.py index 4b64bc3..62b6422 100644 --- a/gdb/python/lib/gdb/function/__init__.py +++ b/gdb/python/lib/gdb/function/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2024 Free Software Foundation, Inc. +# Copyright (C) 2012-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/function/as_string.py b/gdb/python/lib/gdb/function/as_string.py index a255fff..ff1304f 100644 --- a/gdb/python/lib/gdb/function/as_string.py +++ b/gdb/python/lib/gdb/function/as_string.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2024 Free Software Foundation, Inc. +# Copyright (C) 2016-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/function/caller_is.py b/gdb/python/lib/gdb/function/caller_is.py index bacd8c0..3687e4c 100644 --- a/gdb/python/lib/gdb/function/caller_is.py +++ b/gdb/python/lib/gdb/function/caller_is.py @@ -1,5 +1,5 @@ # Caller-is functions. -# Copyright (C) 2008-2024 Free Software Foundation, Inc. +# Copyright (C) 2008-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/function/strfns.py b/gdb/python/lib/gdb/function/strfns.py index 90c9cea..a620f4f 100644 --- a/gdb/python/lib/gdb/function/strfns.py +++ b/gdb/python/lib/gdb/function/strfns.py @@ -1,5 +1,5 @@ # Useful gdb string convenience functions. -# Copyright (C) 2012-2024 Free Software Foundation, Inc. +# Copyright (C) 2012-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py index 2c2ceba..b03aaad 100644 --- a/gdb/python/lib/gdb/missing_debug.py +++ b/gdb/python/lib/gdb/missing_debug.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2024 Free Software Foundation, Inc. +# Copyright (C) 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/missing_files.py b/gdb/python/lib/gdb/missing_files.py index 5f2df88c..9f24db7 100644 --- a/gdb/python/lib/gdb/missing_files.py +++ b/gdb/python/lib/gdb/missing_files.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2024 Free Software Foundation, Inc. +# Copyright (C) 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/missing_objfile.py b/gdb/python/lib/gdb/missing_objfile.py index ace0e13..3d06bdd 100644 --- a/gdb/python/lib/gdb/missing_objfile.py +++ b/gdb/python/lib/gdb/missing_objfile.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Free Software Foundation, Inc. +# Copyright (C) 2024-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/printer/__init__.py b/gdb/python/lib/gdb/printer/__init__.py index 6692044..854ff3a 100644 --- a/gdb/python/lib/gdb/printer/__init__.py +++ b/gdb/python/lib/gdb/printer/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014-2024 Free Software Foundation, Inc. +# Copyright (C) 2014-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/printing.py b/gdb/python/lib/gdb/printing.py index 0635993..cba27d2 100644 --- a/gdb/python/lib/gdb/printing.py +++ b/gdb/python/lib/gdb/printing.py @@ -1,5 +1,5 @@ # Pretty-printer utilities. -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/prompt.py b/gdb/python/lib/gdb/prompt.py index 4ad38e4..060474c 100644 --- a/gdb/python/lib/gdb/prompt.py +++ b/gdb/python/lib/gdb/prompt.py @@ -1,5 +1,5 @@ # Extended prompt utilities. -# Copyright (C) 2011-2024 Free Software Foundation, Inc. +# Copyright (C) 2011-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -""" Extended prompt library functions.""" +"""Extended prompt library functions.""" import os diff --git a/gdb/python/lib/gdb/ptwrite.py b/gdb/python/lib/gdb/ptwrite.py index 3be65fe..fcc72de 100644 --- a/gdb/python/lib/gdb/ptwrite.py +++ b/gdb/python/lib/gdb/ptwrite.py @@ -1,5 +1,5 @@ # Ptwrite utilities. -# Copyright (C) 2023 Free Software Foundation, Inc. +# Copyright (C) 2023-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/styling.py b/gdb/python/lib/gdb/styling.py index 1c5394e..60c470f 100644 --- a/gdb/python/lib/gdb/styling.py +++ b/gdb/python/lib/gdb/styling.py @@ -1,5 +1,5 @@ # Styling related hooks. -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ try: from pygments import formatters, highlight, lexers from pygments.filters import TokenMergeFilter from pygments.token import Comment, Error, Text + from pygments.util import ClassNotFound _formatter = None @@ -31,10 +32,13 @@ try: _formatter = formatters.TerminalFormatter() return _formatter - def colorize(filename, contents): + def colorize(filename, contents, lang): # Don't want any errors. try: - lexer = lexers.get_lexer_for_filename(filename, stripnl=False) + try: + lexer = lexers.get_lexer_by_name(lang, stripnl=False) + except ClassNotFound: + lexer = lexers.get_lexer_for_filename(filename, stripnl=False) formatter = get_formatter() return highlight(contents, lexer, formatter).encode( gdb.host_charset(), "backslashreplace" @@ -76,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()) @@ -94,7 +97,7 @@ try: except ImportError: - def colorize(filename, contents): + def colorize(filename, contents, lang): return None def colorize_disasm(content, gdbarch): diff --git a/gdb/python/lib/gdb/types.py b/gdb/python/lib/gdb/types.py index b4af59c..bac1fb9 100644 --- a/gdb/python/lib/gdb/types.py +++ b/gdb/python/lib/gdb/types.py @@ -1,5 +1,5 @@ # Type utilities. -# Copyright (C) 2010-2024 Free Software Foundation, Inc. +# Copyright (C) 2010-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py index bb0db79..3e1f756 100644 --- a/gdb/python/lib/gdb/unwinder.py +++ b/gdb/python/lib/gdb/unwinder.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2024 Free Software Foundation, Inc. +# Copyright (C) 2015-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/lib/gdb/xmethod.py b/gdb/python/lib/gdb/xmethod.py index e12d51c..310585a 100644 --- a/gdb/python/lib/gdb/xmethod.py +++ b/gdb/python/lib/gdb/xmethod.py @@ -1,5 +1,5 @@ # Python side of the support for xmethods. -# Copyright (C) 2013-2024 Free Software Foundation, Inc. +# Copyright (C) 2013-2025 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def index f315e75..c20bb36 100644 --- a/gdb/python/py-all-events.def +++ b/gdb/python/py-all-events.def @@ -1,6 +1,6 @@ /* Python event definitions -*- c++ -*- - Copyright (C) 2017-2024 Free Software Foundation, Inc. + Copyright (C) 2017-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c index f7e35a4..2bf6251 100644 --- a/gdb/python/py-arch.c +++ b/gdb/python/py-arch.c @@ -1,6 +1,6 @@ /* Python interface to architecture - Copyright (C) 2013-2024 Free Software Foundation, Inc. + Copyright (C) 2013-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -318,6 +318,16 @@ archpy_integer_type (PyObject *self, PyObject *args, PyObject *kw) return type_to_type_object (type); } +/* Implementation of gdb.void_type. */ +static PyObject * +archpy_void_type (PyObject *self, PyObject *args) +{ + struct gdbarch *gdbarch; + ARCHPY_REQUIRE_VALID (self, gdbarch); + + return type_to_type_object (builtin_type (gdbarch)->builtin_void); +} + /* __repr__ implementation for gdb.Architecture. */ static PyObject * @@ -383,6 +393,10 @@ END_PC." }, "integer_type (size [, signed]) -> type\n\ Return an integer Type corresponding to the given bitsize and signed-ness.\n\ If not specified, the type defaults to signed." }, + { "void_type", (PyCFunction) archpy_void_type, + METH_NOARGS, + "void_type () -> type\n\ +Return a void Type." }, { "registers", (PyCFunction) archpy_registers, METH_VARARGS | METH_KEYWORDS, "registers ([ group-name ]) -> Iterator.\n\ diff --git a/gdb/python/py-auto-load.c b/gdb/python/py-auto-load.c index 7dc3a7b..f65b979 100644 --- a/gdb/python/py-auto-load.c +++ b/gdb/python/py-auto-load.c @@ -1,6 +1,6 @@ /* GDB routines for supporting auto-loaded scripts. - Copyright (C) 2010-2024 Free Software Foundation, Inc. + Copyright (C) 2010-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-block.c b/gdb/python/py-block.c index aeb9acb..fa7dd19 100644 --- a/gdb/python/py-block.c +++ b/gdb/python/py-block.c @@ -1,6 +1,6 @@ /* Python interface to blocks. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -149,6 +149,37 @@ blpy_get_superblock (PyObject *self, void *closure) Py_RETURN_NONE; } +/* Implement gdb.Block.subblocks attribute. Return a list of gdb.Block + objects that are direct children of this block. */ + +static PyObject * +blpy_get_subblocks (PyObject *self, void *closure) +{ + const struct block *block; + + BLPY_REQUIRE_VALID (self, block); + + gdbpy_ref<> list (PyList_New (0)); + if (list == nullptr) + return nullptr; + + compunit_symtab *cu = block->global_block ()->compunit (); + + for (const struct block *each : cu->blockvector ()->blocks ()) + { + if (each->superblock () == block) + { + gdbpy_ref<> item (block_to_block_object (each, cu->objfile ())); + + if (item.get () == nullptr + || PyList_Append (list.get (), item.get ()) == -1) + return nullptr; + } + } + + return list.release (); +} + /* Return the global block associated to this block. */ static PyObject * @@ -529,6 +560,8 @@ static gdb_PyGetSetDef block_object_getset[] = { "Whether this block is a static block.", NULL }, { "is_global", blpy_is_global, NULL, "Whether this block is a global block.", NULL }, + { "subblocks", blpy_get_subblocks, nullptr, + "List of blocks contained in this block.", nullptr }, { NULL } /* Sentinel */ }; diff --git a/gdb/python/py-bpevent.c b/gdb/python/py-bpevent.c index 5982a26..ddf5066 100644 --- a/gdb/python/py-bpevent.c +++ b/gdb/python/py-bpevent.c @@ -1,6 +1,6 @@ /* Python interface to inferior breakpoint stop events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index 75f50e1..9ce8671 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -1,6 +1,6 @@ /* Python interface to breakpoints - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "source.h" #include "value.h" #include "python-internal.h" #include "python.h" @@ -947,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; } } @@ -1536,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, @@ -1629,6 +1628,26 @@ bplocpy_get_owner (PyObject *py_self, void *closure) return (PyObject *) self->owner; } +/* Attempt to get fully resolved file path for symtab. */ + +static gdbpy_ref<> +bploc_filepath (struct symtab *bploc_symtab) +{ + /* The exception is not ours to handle. We should always + return some string value and filename is never null. */ + try + { + const char *full = symtab_to_fullname (bploc_symtab); + if (full) + return host_string_to_python_string (full); + } + catch (const gdb_exception &except) + { + } + + return host_string_to_python_string (bploc_symtab->filename); +} + /* Python function to get the source file name path and line number where this breakpoint location was set. */ @@ -1643,9 +1662,7 @@ bplocpy_get_source_location (PyObject *py_self, void *closure) gdbpy_ref<> tup (PyTuple_New (2)); if (tup == nullptr) return nullptr; - /* symtab->filename is never NULL. */ - gdbpy_ref<> filename - = host_string_to_python_string (self->bp_loc->symtab->filename); + gdbpy_ref<> filename = bploc_filepath (self->bp_loc->symtab); if (filename == nullptr) return nullptr; auto line = gdb_py_object_from_ulongest (self->bp_loc->line_number); diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index 2bb9b82..dc5e270 100644 --- a/gdb/python/py-cmd.c +++ b/gdb/python/py-cmd.c @@ -1,6 +1,6 @@ /* gdb commands implemented in Python - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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 3a90b62..3bbd22d 100644 --- a/gdb/python/py-color.c +++ b/gdb/python/py-color.c @@ -1,6 +1,6 @@ /* Python interface to ui_file_style::color objects. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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-color.h b/gdb/python/py-color.h index a778d5b..d94baa2 100644 --- a/gdb/python/py-color.h +++ b/gdb/python/py-color.h @@ -1,6 +1,6 @@ /* Python interface to ui_file_style::color objects. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PYTHON_PY_COLOR_H -#define PYTHON_PY_COLOR_H +#ifndef GDB_PYTHON_PY_COLOR_H +#define GDB_PYTHON_PY_COLOR_H #include "python-internal.h" #include "ui-style.h" @@ -32,4 +32,4 @@ extern bool gdbpy_is_color (PyObject *obj); /* Extracts value from OBJ object of gdb.Color type. */ extern const ui_file_style::color &gdbpy_get_color (PyObject *obj); -#endif /* PYTHON_PY_COLOR_H */ +#endif /* GDB_PYTHON_PY_COLOR_H */ diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c index 7b5146d..a6d9ad0 100644 --- a/gdb/python/py-connection.c +++ b/gdb/python/py-connection.c @@ -1,6 +1,6 @@ /* Python interface to inferiors. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -27,8 +27,7 @@ #include "arch-utils.h" #include "remote.h" #include "charset.h" - -#include <map> +#include "gdbsupport/unordered_map.h" /* The Python object that represents a connection. */ @@ -65,8 +64,8 @@ extern PyTypeObject remote_connection_object_type /* A map between process_stratum targets and the Python object representing them. We actually hold a gdbpy_ref around the Python object so that reference counts are handled correctly when entries are deleted. */ -static std::map<process_stratum_target *, - gdbpy_ref<connection_object>> all_connection_objects; +static gdb::unordered_map<process_stratum_target *, + gdbpy_ref<connection_object>> all_connection_objects; /* Return a reference to a gdb.TargetConnection object for TARGET. If TARGET is nullptr then a reference to None is returned. @@ -429,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-continueevent.c b/gdb/python/py-continueevent.c index c859b1e..500b5da 100644 --- a/gdb/python/py-continueevent.c +++ b/gdb/python/py-continueevent.c @@ -1,6 +1,6 @@ /* Python interface to inferior continue events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-dap.c b/gdb/python/py-dap.c index d5555c9..9a9875a 100644 --- a/gdb/python/py-dap.c +++ b/gdb/python/py-dap.c @@ -1,6 +1,6 @@ /* Python DAP interpreter - Copyright (C) 2022-2024 Free Software Foundation, Inc. + Copyright (C) 2022-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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 66ed456..17064dc 100644 --- a/gdb/python/py-disasm.c +++ b/gdb/python/py-disasm.c @@ -1,6 +1,6 @@ /* Python interface to instruction disassembly. - Copyright (C) 2021-2024 Free Software Foundation, Inc. + Copyright (C) 2021-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -133,7 +133,7 @@ static bool python_print_insn_enabled = false; struct gdbpy_disassembler : public gdb_disassemble_info { /* Constructor. */ - gdbpy_disassembler (disasm_info_object *obj, PyObject *memory_source); + gdbpy_disassembler (disasm_info_object *obj); /* Get the DisassembleInfo object pointer. */ disasm_info_object * @@ -222,11 +222,6 @@ private: address of the memory error is stored in here. */ std::optional<CORE_ADDR> m_memory_error_address; - /* When the user calls the builtin_disassemble function, if they pass a - memory source object then a pointer to the object is placed in here, - otherwise, this field is nullptr. */ - PyObject *m_memory_source; - /* Move the exception EX into this disassembler object. */ void store_exception (gdbpy_err_fetch &&ex) { @@ -539,18 +534,17 @@ disasmpy_init_disassembler_result (disasm_result_object *obj, int length, static PyObject * disasmpy_builtin_disassemble (PyObject *self, PyObject *args, PyObject *kw) { - PyObject *info_obj, *memory_source_obj = nullptr; - static const char *keywords[] = { "info", "memory_source", nullptr }; - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!|O", keywords, - &disasm_info_object_type, &info_obj, - &memory_source_obj)) + PyObject *info_obj; + static const char *keywords[] = { "info", nullptr }; + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!", keywords, + &disasm_info_object_type, &info_obj)) return nullptr; disasm_info_object *disasm_info = (disasm_info_object *) info_obj; DISASMPY_DISASM_INFO_REQUIRE_VALID (disasm_info); /* Where the result will be written. */ - gdbpy_disassembler disassembler (disasm_info, memory_source_obj); + gdbpy_disassembler disassembler (disasm_info); /* Now actually perform the disassembly. LENGTH is set to the length of the disassembled instruction, or -1 if there was a memory-error @@ -1139,16 +1133,14 @@ gdbpy_disassembler::print_address_func (bfd_vma addr, /* constructor. */ -gdbpy_disassembler::gdbpy_disassembler (disasm_info_object *obj, - PyObject *memory_source) +gdbpy_disassembler::gdbpy_disassembler (disasm_info_object *obj) : gdb_disassemble_info (obj->gdbarch, read_memory_func, memory_error_func, print_address_func, fprintf_func, fprintf_styled_func), - m_disasm_info_object (obj), - m_memory_source (memory_source) + m_disasm_info_object (obj) { /* Nothing. */ } /* A wrapper around a reference to a Python DisassembleInfo object, which @@ -1319,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); } @@ -1610,10 +1603,9 @@ PyMethodDef python_disassembler_methods[] = { { "builtin_disassemble", (PyCFunction) disasmpy_builtin_disassemble, METH_VARARGS | METH_KEYWORDS, - "builtin_disassemble (INFO, MEMORY_SOURCE = None) -> None\n\ + "builtin_disassemble (INFO) -> None\n\ Disassemble using GDB's builtin disassembler. INFO is an instance of\n\ -gdb.disassembler.DisassembleInfo. The MEMORY_SOURCE, if not None, should\n\ -be an object with the read_memory method." }, +gdb.disassembler.DisassembleInfo." }, { "_set_enabled", (PyCFunction) disasmpy_set_enabled, METH_VARARGS | METH_KEYWORDS, "_set_enabled (STATE) -> None\n\ diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def index e22f042..15cd9fa 100644 --- a/gdb/python/py-event-types.def +++ b/gdb/python/py-event-types.def @@ -1,6 +1,6 @@ /* Python event definitions -*- c++ -*- - Copyright (C) 2017-2024 Free Software Foundation, Inc. + Copyright (C) 2017-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-event.c b/gdb/python/py-event.c index a918136..da3b52c 100644 --- a/gdb/python/py-event.c +++ b/gdb/python/py-event.c @@ -1,6 +1,6 @@ /* Python interface to inferior events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h index e4491db..3938368 100644 --- a/gdb/python/py-event.h +++ b/gdb/python/py-event.h @@ -1,6 +1,6 @@ /* Python interface to inferior events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-events.h b/gdb/python/py-events.h index b44c437..d9df3ab 100644 --- a/gdb/python/py-events.h +++ b/gdb/python/py-events.h @@ -1,6 +1,6 @@ /* Python interface to inferior events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-evtregistry.c b/gdb/python/py-evtregistry.c index 7ae3997..2166f75 100644 --- a/gdb/python/py-evtregistry.c +++ b/gdb/python/py-evtregistry.c @@ -1,6 +1,6 @@ /* Python interface to inferior thread event registries. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-evts.c b/gdb/python/py-evts.c index 4e966e1..c905926 100644 --- a/gdb/python/py-evts.c +++ b/gdb/python/py-evts.c @@ -1,6 +1,6 @@ /* Python interface to inferior events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-exitedevent.c b/gdb/python/py-exitedevent.c index 4837468..dfe8a8f 100644 --- a/gdb/python/py-exitedevent.c +++ b/gdb/python/py-exitedevent.c @@ -1,6 +1,6 @@ /* Python interface to inferior exit events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c index bc53d4e..0ea629f 100644 --- a/gdb/python/py-finishbreakpoint.c +++ b/gdb/python/py-finishbreakpoint.c @@ -1,6 +1,6 @@ /* Python interface to finish breakpoints - Copyright (C) 2011-2024 Free Software Foundation, Inc. + Copyright (C) 2011-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c index 88646ee..722a952 100644 --- a/gdb/python/py-frame.c +++ b/gdb/python/py-frame.c @@ -1,6 +1,6 @@ /* Python interface to stack frames - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c index 24e1c91..adf4233 100644 --- a/gdb/python/py-framefilter.c +++ b/gdb/python/py-framefilter.c @@ -1,6 +1,6 @@ /* Python frame filters - Copyright (C) 2013-2024 Free Software Foundation, Inc. + Copyright (C) 2013-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -746,7 +746,7 @@ using levels_printed_hash = gdb::unordered_set<frame_info *>; OUT is the output stream to print to. - INDENT is the level of indention for this frame (in the case of elided + INDENT is the level of indentation for this frame (in the case of elided frames). LEVELS_PRINTED is a hash-table containing all the frames for which the @@ -834,7 +834,7 @@ py_print_frame (PyObject *filter, frame_filter_flags flags, if (print_frame_info) { /* Elided frames are also printed with this function (recursively) - and are printed with indention. */ + and are printed with indentation. */ if (indent > 0) out->spaces (indent * 4); diff --git a/gdb/python/py-function.c b/gdb/python/py-function.c index 2b65d8b..86a2f30 100644 --- a/gdb/python/py-function.c +++ b/gdb/python/py-function.c @@ -1,6 +1,6 @@ /* Convenience functions implemented in Python. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-gdb-readline.c b/gdb/python/py-gdb-readline.c index dd0ee45..70ceebb 100644 --- a/gdb/python/py-gdb-readline.c +++ b/gdb/python/py-gdb-readline.c @@ -1,6 +1,6 @@ /* Readline support for Python. - Copyright (C) 2012-2024 Free Software Foundation, Inc. + Copyright (C) 2012-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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-inferior.c b/gdb/python/py-inferior.c index 60bf56d..2aa11d3 100644 --- a/gdb/python/py-inferior.c +++ b/gdb/python/py-inferior.c @@ -1,6 +1,6 @@ /* Python interface to inferiors. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -30,10 +30,10 @@ #include "py-event.h" #include "py-stopevent.h" #include "progspace-and-thread.h" -#include <unordered_map> +#include "gdbsupport/unordered_map.h" using thread_map_t - = std::unordered_map<thread_info *, gdbpy_ref<thread_object>>; + = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>; struct inferior_object { @@ -929,7 +929,7 @@ infpy_set_args (PyObject *self, PyObject *value, void *closure) for (const auto &arg : args) argvec.push_back (arg.get ()); gdb::array_view<char * const> view (argvec.data (), argvec.size ()); - inf->inferior->set_args (view); + inf->inferior->set_args (view, true); } else { diff --git a/gdb/python/py-infevents.c b/gdb/python/py-infevents.c index f53d589..e63ba52 100644 --- a/gdb/python/py-infevents.c +++ b/gdb/python/py-infevents.c @@ -1,6 +1,6 @@ /* Python interface to inferior function events. - Copyright (C) 2013-2024 Free Software Foundation, Inc. + Copyright (C) 2013-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c index 4340666..4f1f8d4 100644 --- a/gdb/python/py-infthread.c +++ b/gdb/python/py-infthread.c @@ -1,6 +1,6 @@ /* Python interface to inferior threads. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-instruction.c b/gdb/python/py-instruction.c index 7d77572..60cd561 100644 --- a/gdb/python/py-instruction.c +++ b/gdb/python/py-instruction.c @@ -1,6 +1,6 @@ /* Python interface to instruction objects. - Copyright 2017-2024 Free Software Foundation, Inc. + Copyright 2017-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-instruction.h b/gdb/python/py-instruction.h index efbdda8..8baf9cb 100644 --- a/gdb/python/py-instruction.h +++ b/gdb/python/py-instruction.h @@ -1,6 +1,6 @@ /* Python interface to instruction objects. - Copyright 2017-2024 Free Software Foundation, Inc. + Copyright 2017-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-lazy-string.c b/gdb/python/py-lazy-string.c index 1226c28..265f34a 100644 --- a/gdb/python/py-lazy-string.c +++ b/gdb/python/py-lazy-string.c @@ -1,6 +1,6 @@ /* Python interface to lazy strings. - Copyright (C) 2010-2024 Free Software Foundation, Inc. + Copyright (C) 2010-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -32,7 +32,7 @@ struct lazy_string_object { /* Holds the encoding that will be applied to the string when the string is printed by GDB. If the encoding is set to None then GDB will select the most appropriate - encoding when the sting is printed. */ + encoding when the string is printed. */ char *encoding; /* If TYPE is an array: If the length is known, then this value is the diff --git a/gdb/python/py-linetable.c b/gdb/python/py-linetable.c index fc57f36..90cba09 100644 --- a/gdb/python/py-linetable.c +++ b/gdb/python/py-linetable.c @@ -1,6 +1,6 @@ /* Python interface to line tables. - Copyright (C) 2013-2024 Free Software Foundation, Inc. + Copyright (C) 2013-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-membuf.c b/gdb/python/py-membuf.c index 25ebc99..817dfba 100644 --- a/gdb/python/py-membuf.c +++ b/gdb/python/py-membuf.c @@ -1,6 +1,6 @@ /* Python memory buffer interface for reading inferior memory. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c index cf75a18..9b871d4 100644 --- a/gdb/python/py-mi.c +++ b/gdb/python/py-mi.c @@ -1,6 +1,6 @@ /* Python interface to MI commands - Copyright (C) 2023-2024 Free Software Foundation, Inc. + Copyright (C) 2023-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index e051b90..72f427f 100644 --- a/gdb/python/py-micmd.c +++ b/gdb/python/py-micmd.c @@ -1,6 +1,6 @@ /* MI Command Set for GDB, the GNU debugger. - Copyright (C) 2019-2024 Free Software Foundation, Inc. + Copyright (C) 2019-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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-newobjfileevent.c b/gdb/python/py-newobjfileevent.c index deaee70..b2071f96 100644 --- a/gdb/python/py-newobjfileevent.c +++ b/gdb/python/py-newobjfileevent.c @@ -1,6 +1,6 @@ /* Python interface to new object file loading events. - Copyright (C) 2011-2024 Free Software Foundation, Inc. + Copyright (C) 2011-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c index 6ce58a1..1c6f569 100644 --- a/gdb/python/py-objfile.c +++ b/gdb/python/py-objfile.c @@ -1,6 +1,6 @@ /* Python interface to objfiles. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index 0e79d21..06237b6 100644 --- a/gdb/python/py-param.c +++ b/gdb/python/py-param.c @@ -1,6 +1,6 @@ /* GDB parameters implemented in Python - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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-prettyprint.c b/gdb/python/py-prettyprint.c index e061ea1..1e72e90 100644 --- a/gdb/python/py-prettyprint.c +++ b/gdb/python/py-prettyprint.c @@ -1,6 +1,6 @@ /* Python pretty-printing - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c index bb44aa4..61f9b4b 100644 --- a/gdb/python/py-progspace.c +++ b/gdb/python/py-progspace.c @@ -1,6 +1,6 @@ /* Python interface to program spaces. - Copyright (C) 2010-2024 Free Software Foundation, Inc. + Copyright (C) 2010-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -116,7 +116,7 @@ pspy_get_filename (PyObject *self, void *closure) Py_RETURN_NONE; } -/* Implement the gdb.Progspace.symbol_file attribute. Retun the +/* Implement the gdb.Progspace.symbol_file attribute. Return the gdb.Objfile corresponding to the currently loaded symbol-file, or None if no symbol-file is loaded. If the Progspace is invalid then raise an exception. */ @@ -136,7 +136,7 @@ pspy_get_symbol_file (PyObject *self, void *closure) Py_RETURN_NONE; } -/* Implement the gdb.Progspace.executable_filename attribute. Retun a +/* Implement the gdb.Progspace.executable_filename attribute. Return a string containing the name of the current executable, or None if no executable is currently set. If the Progspace is invalid then raise an exception. */ diff --git a/gdb/python/py-record-btrace.c b/gdb/python/py-record-btrace.c index bdfebf1..620f033 100644 --- a/gdb/python/py-record-btrace.c +++ b/gdb/python/py-record-btrace.c @@ -1,6 +1,6 @@ /* Python interface to btrace instruction history. - Copyright 2016-2024 Free Software Foundation, Inc. + Copyright 2016-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-record-btrace.h b/gdb/python/py-record-btrace.h index 30465bb..e5a1b10 100644 --- a/gdb/python/py-record-btrace.h +++ b/gdb/python/py-record-btrace.h @@ -1,6 +1,6 @@ /* Python interface to btrace record targets. - Copyright 2016-2024 Free Software Foundation, Inc. + Copyright 2016-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-record-full.c b/gdb/python/py-record-full.c index b6623a6..dd23055 100644 --- a/gdb/python/py-record-full.c +++ b/gdb/python/py-record-full.c @@ -1,6 +1,6 @@ /* Python interface to btrace instruction history. - Copyright 2016-2024 Free Software Foundation, Inc. + Copyright 2016-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-record-full.h b/gdb/python/py-record-full.h index df95776..aa07b3a 100644 --- a/gdb/python/py-record-full.h +++ b/gdb/python/py-record-full.h @@ -1,6 +1,6 @@ /* Python interface to full record targets. - Copyright 2016-2024 Free Software Foundation, Inc. + Copyright 2016-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-record.c b/gdb/python/py-record.c index 2995dc1..7e7904b 100644 --- a/gdb/python/py-record.c +++ b/gdb/python/py-record.c @@ -1,6 +1,6 @@ /* Python interface to record targets. - Copyright 2016-2024 Free Software Foundation, Inc. + Copyright 2016-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-record.h b/gdb/python/py-record.h index 1492de6..98e4863 100644 --- a/gdb/python/py-record.h +++ b/gdb/python/py-record.h @@ -1,6 +1,6 @@ /* Python interface to record targets. - Copyright 2017-2024 Free Software Foundation, Inc. + Copyright 2017-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h index 938068e..04e8b82 100644 --- a/gdb/python/py-ref.h +++ b/gdb/python/py-ref.h @@ -1,6 +1,6 @@ /* Python reference-holding class - Copyright (C) 2016-2024 Free Software Foundation, Inc. + Copyright (C) 2016-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c index 229dd62..9be2e3c 100644 --- a/gdb/python/py-registers.c +++ b/gdb/python/py-registers.c @@ -1,6 +1,6 @@ /* Python interface to register, and register group information. - Copyright (C) 2020-2024 Free Software Foundation, Inc. + Copyright (C) 2020-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -22,7 +22,7 @@ #include "reggroups.h" #include "python-internal.h" #include "user-regs.h" -#include <unordered_map> +#include "gdbsupport/unordered_map.h" /* Per-gdbarch data type. */ typedef std::vector<gdbpy_ref<>> gdbpy_register_type; @@ -98,7 +98,7 @@ gdbpy_get_reggroup (const reggroup *reggroup) /* Map from GDB's internal reggroup objects to the Python representation. GDB's reggroups are global, and are never deleted, so using a map like this is safe. */ - static std::unordered_map<const struct reggroup *,gdbpy_ref<>> + static gdb::unordered_map<const struct reggroup *,gdbpy_ref<>> gdbpy_reggroup_object_map; /* If there is not already a suitable Python object in the map then @@ -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-signalevent.c b/gdb/python/py-signalevent.c index bf51106..5d58aa1 100644 --- a/gdb/python/py-signalevent.c +++ b/gdb/python/py-signalevent.c @@ -1,6 +1,6 @@ /* Python interface to inferior signal stop events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-stopevent.c b/gdb/python/py-stopevent.c index 485bbb1..e49f240 100644 --- a/gdb/python/py-stopevent.c +++ b/gdb/python/py-stopevent.c @@ -1,6 +1,6 @@ /* Python interface to inferior stop events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-stopevent.h b/gdb/python/py-stopevent.h index 450d8c2..fbfa87e 100644 --- a/gdb/python/py-stopevent.h +++ b/gdb/python/py-stopevent.h @@ -1,6 +1,6 @@ /* Python interface to inferior events. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-symbol.c b/gdb/python/py-symbol.c index f1ba0ba..3028a30 100644 --- a/gdb/python/py-symbol.c +++ b/gdb/python/py-symbol.c @@ -1,6 +1,6 @@ /* Python interface to symbols. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -29,12 +29,6 @@ struct symbol_object { PyObject_HEAD /* The GDB symbol structure this object is wrapping. */ struct symbol *symbol; - /* A symbol object is associated with an objfile, so keep track with - doubly-linked list, rooted in the objfile. This lets us - invalidate the underlying struct symbol when the objfile is - deleted. */ - symbol_object *prev; - symbol_object *next; }; /* Require a valid symbol. All access to symbol_object->symbol should be @@ -50,26 +44,8 @@ struct symbol_object { } \ } while (0) -/* A deleter that is used when an objfile is about to be freed. */ -struct symbol_object_deleter -{ - void operator() (symbol_object *obj) - { - while (obj) - { - symbol_object *next = obj->next; - - obj->symbol = NULL; - obj->next = NULL; - obj->prev = NULL; - - obj = next; - } - } -}; - -static const registry<objfile>::key<symbol_object, symbol_object_deleter> - sympy_objfile_data_key; +static const gdbpy_registry<gdbpy_memoizing_registry_storage<symbol_object, + symbol, &symbol_object::symbol>> sympy_registry; static PyObject * sympy_str (PyObject *self) @@ -153,6 +129,19 @@ sympy_get_addr_class (PyObject *self, void *closure) return gdb_py_object_from_longest (symbol->aclass ()).release (); } +/* Implement gdb.Symbol.domain attribute. Return the domain as an + integer. */ + +static PyObject * +sympy_get_domain (PyObject *self, void *closure) +{ + struct symbol *symbol = nullptr; + + SYMPY_REQUIRE_VALID (self, symbol); + + return gdb_py_object_from_longest (symbol->domain ()).release (); +} + static PyObject * sympy_is_argument (PyObject *self, void *closure) { @@ -334,19 +323,18 @@ static void set_symbol (symbol_object *obj, struct symbol *symbol) { obj->symbol = symbol; - obj->prev = NULL; - if (symbol->is_objfile_owned () - && symbol->symtab () != NULL) + if (symbol->is_objfile_owned ()) { - struct objfile *objfile = symbol->objfile (); - - obj->next = sympy_objfile_data_key.get (objfile); - if (obj->next) - obj->next->prev = obj; - sympy_objfile_data_key.set (objfile, obj); + /* Can it really happen that symbol->symtab () is NULL? */ + if (symbol->symtab () != nullptr) + { + sympy_registry.add (symbol->objfile (), obj); + } } else - obj->next = NULL; + { + sympy_registry.add (symbol->arch (), obj); + } } /* Create a new symbol object (gdb.Symbol) that encapsulates the struct @@ -356,6 +344,15 @@ symbol_to_symbol_object (struct symbol *sym) { symbol_object *sym_obj; + /* Look if there's already a gdb.Symbol object for given SYMBOL + and if so, return it. */ + if (sym->is_objfile_owned ()) + sym_obj = sympy_registry.lookup (sym->objfile (), sym); + else + sym_obj = sympy_registry.lookup (sym->arch (), sym); + if (sym_obj != nullptr) + return (PyObject*)sym_obj; + sym_obj = PyObject_New (symbol_object, &symbol_object_type); if (sym_obj) set_symbol (sym_obj, sym); @@ -377,15 +374,14 @@ sympy_dealloc (PyObject *obj) { symbol_object *sym_obj = (symbol_object *) obj; - if (sym_obj->prev) - sym_obj->prev->next = sym_obj->next; - else if (sym_obj->symbol != NULL - && sym_obj->symbol->is_objfile_owned () - && sym_obj->symbol->symtab () != NULL) - sympy_objfile_data_key.set (sym_obj->symbol->objfile (), sym_obj->next); - if (sym_obj->next) - sym_obj->next->prev = sym_obj->prev; - sym_obj->symbol = NULL; + if (sym_obj->symbol != nullptr) + { + if (sym_obj->symbol->is_objfile_owned ()) + sympy_registry.remove (sym_obj->symbol->objfile (), sym_obj); + else + sympy_registry.remove (sym_obj->symbol->arch (), sym_obj); + } + Py_TYPE (obj)->tp_free (obj); } @@ -719,6 +715,7 @@ static gdb_PyGetSetDef symbol_object_getset[] = { This is either name or linkage_name, depending on whether the user asked GDB\n\ to display demangled or mangled names.", NULL }, { "addr_class", sympy_get_addr_class, NULL, "Address class of the symbol." }, + { "domain", sympy_get_domain, nullptr, "Domain of the symbol." }, { "is_argument", sympy_is_argument, NULL, "True if the symbol is an argument of a function." }, { "is_artificial", sympy_is_artificial, nullptr, diff --git a/gdb/python/py-symtab.c b/gdb/python/py-symtab.c index 99a5094..9f7bdb0 100644 --- a/gdb/python/py-symtab.c +++ b/gdb/python/py-symtab.c @@ -1,6 +1,6 @@ /* Python interface to symbol tables. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -28,39 +28,12 @@ struct symtab_object { PyObject_HEAD /* The GDB Symbol table structure. */ struct symtab *symtab; - /* A symtab object is associated with an objfile, so keep track with - a doubly-linked list, rooted in the objfile. This allows - invalidation of the underlying struct symtab when the objfile is - deleted. */ - symtab_object *prev; - symtab_object *next; -}; - -/* This function is called when an objfile is about to be freed. - Invalidate the symbol table as further actions on the symbol table - would result in bad data. All access to obj->symtab should be - gated by STPY_REQUIRE_VALID which will raise an exception on - invalid symbol tables. */ -struct stpy_deleter -{ - void operator() (symtab_object *obj) - { - while (obj) - { - symtab_object *next = obj->next; - - obj->symtab = NULL; - obj->next = NULL; - obj->prev = NULL; - obj = next; - } - } }; extern PyTypeObject symtab_object_type CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("symtab_object"); -static const registry<objfile>::key<symtab_object, stpy_deleter> - stpy_objfile_data_key; +static const gdbpy_registry<gdbpy_memoizing_registry_storage<symtab_object, + symtab, &symtab_object::symtab>> stpy_registry; /* Require a valid symbol table. All access to symtab_object->symtab should be gated by this call. */ @@ -77,8 +50,6 @@ static const registry<objfile>::key<symtab_object, stpy_deleter> struct sal_object { PyObject_HEAD - /* The GDB Symbol table structure. */ - PyObject *symtab; /* The GDB Symbol table and line structure. */ struct symtab_and_line *sal; /* A Symtab and line object is associated with an objfile, so keep @@ -94,34 +65,19 @@ struct sal_object { data. All access to obj->sal should be gated by SALPY_REQUIRE_VALID which will raise an exception on invalid symbol table and line objects. */ -struct salpy_deleter +struct salpy_invalidator { void operator() (sal_object *obj) { - gdbpy_enter enter_py; - - while (obj) - { - sal_object *next = obj->next; - - gdbpy_ref<> tmp (obj->symtab); - obj->symtab = Py_None; - Py_INCREF (Py_None); - - obj->next = NULL; - obj->prev = NULL; - xfree (obj->sal); - obj->sal = NULL; - - obj = next; - } + xfree (obj->sal); + obj->sal = nullptr; } }; extern PyTypeObject sal_object_type CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sal_object"); -static const registry<objfile>::key<sal_object, salpy_deleter> - salpy_objfile_data_key; +static const gdbpy_registry<gdbpy_tracking_registry_storage<sal_object, + symtab_and_line, &sal_object::sal, salpy_invalidator>> salpy_registry; /* Require a valid symbol table and line object. All access to sal_object->sal should be gated by this call. */ @@ -272,18 +228,15 @@ salpy_str (PyObject *self) { const char *filename; sal_object *sal_obj; - struct symtab_and_line *sal = NULL; + struct symtab_and_line *sal = nullptr; SALPY_REQUIRE_VALID (self, sal); sal_obj = (sal_object *) self; - if (sal_obj->symtab == Py_None) + if (sal_obj->sal->symtab == nullptr) filename = "<unknown>"; else - { - symtab *symtab = symtab_object_to_symtab (sal_obj->symtab); - filename = symtab_to_filename_for_display (symtab); - } + filename = symtab_to_filename_for_display (sal_obj->sal->symtab); return PyUnicode_FromFormat ("symbol and line for %s, line %d", filename, sal->line); @@ -292,16 +245,12 @@ salpy_str (PyObject *self) static void stpy_dealloc (PyObject *obj) { - symtab_object *symtab = (symtab_object *) obj; - - if (symtab->prev) - symtab->prev->next = symtab->next; - else if (symtab->symtab) - stpy_objfile_data_key.set (symtab->symtab->compunit ()->objfile (), - symtab->next); - if (symtab->next) - symtab->next->prev = symtab->prev; - symtab->symtab = NULL; + symtab_object *symtab_obj = (symtab_object *) obj; + + if (symtab_obj->symtab != nullptr) + stpy_registry.remove (symtab_obj->symtab->compunit ()->objfile(), + symtab_obj); + Py_TYPE (obj)->tp_free (obj); } @@ -346,13 +295,13 @@ static PyObject * salpy_get_symtab (PyObject *self, void *closure) { struct symtab_and_line *sal; - sal_object *self_sal = (sal_object *) self; SALPY_REQUIRE_VALID (self, sal); - Py_INCREF (self_sal->symtab); - - return (PyObject *) self_sal->symtab; + if (sal->symtab == nullptr) + Py_RETURN_NONE; + else + return symtab_to_symtab_object (sal->symtab); } /* Implementation of gdb.Symtab_and_line.is_valid (self) -> Boolean. @@ -375,17 +324,10 @@ salpy_dealloc (PyObject *self) { sal_object *self_sal = (sal_object *) self; - if (self_sal->prev) - self_sal->prev->next = self_sal->next; - else if (self_sal->symtab != Py_None) - salpy_objfile_data_key.set - (symtab_object_to_symtab (self_sal->symtab)->compunit ()->objfile (), - self_sal->next); - - if (self_sal->next) - self_sal->next->prev = self_sal->prev; + if (self_sal->sal != nullptr && self_sal->sal->symtab != nullptr) + salpy_registry.remove (self_sal->sal->symtab->compunit ()->objfile (), + self_sal); - Py_DECREF (self_sal->symtab); xfree (self_sal->sal); Py_TYPE (self)->tp_free (self); } @@ -395,48 +337,20 @@ salpy_dealloc (PyObject *self) Also, register the sal_object life-cycle with the life-cycle of the object file associated with this sal, if needed. If a failure occurs during the sal population, this function will return -1. */ -static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION +static void set_sal (sal_object *sal_obj, struct symtab_and_line sal) { - PyObject *symtab_obj; - - if (sal.symtab) - { - symtab_obj = symtab_to_symtab_object (sal.symtab); - /* If a symtab existed in the sal, but it cannot be duplicated, - we exit. */ - if (symtab_obj == NULL) - return -1; - } - else - { - symtab_obj = Py_None; - Py_INCREF (Py_None); - } - sal_obj->sal = ((struct symtab_and_line *) xmemdup (&sal, sizeof (struct symtab_and_line), sizeof (struct symtab_and_line))); - sal_obj->symtab = symtab_obj; - sal_obj->prev = NULL; + sal_obj->prev = nullptr; + sal_obj->next = nullptr; /* If the SAL does not have a symtab, we do not add it to the objfile cleanup observer linked list. */ - if (sal_obj->symtab != Py_None) - { - symtab *symtab = symtab_object_to_symtab (sal_obj->symtab); - - sal_obj->next - = salpy_objfile_data_key.get (symtab->compunit ()->objfile ()); - if (sal_obj->next) - sal_obj->next->prev = sal_obj; - - salpy_objfile_data_key.set (symtab->compunit ()->objfile (), sal_obj); - } - else - sal_obj->next = NULL; - - return 0; + symtab *symtab = sal_obj->sal->symtab; + if (symtab != nullptr) + salpy_registry.add (symtab->compunit ()->objfile (), sal_obj); } /* Given a symtab, and a symtab_object that has previously been @@ -448,16 +362,8 @@ static void set_symtab (symtab_object *obj, struct symtab *symtab) { obj->symtab = symtab; - obj->prev = NULL; - if (symtab) - { - obj->next = stpy_objfile_data_key.get (symtab->compunit ()->objfile ()); - if (obj->next) - obj->next->prev = obj; - stpy_objfile_data_key.set (symtab->compunit ()->objfile (), obj); - } - else - obj->next = NULL; + if (symtab != nullptr) + stpy_registry.add (symtab->compunit ()->objfile (), obj); } /* Create a new symbol table (gdb.Symtab) object that encapsulates the @@ -467,6 +373,16 @@ symtab_to_symtab_object (struct symtab *symtab) { symtab_object *symtab_obj; + /* Look if there's already a gdb.Symtab object for given SYMTAB + and if so, return it. */ + if (symtab != nullptr) + { + symtab_obj = stpy_registry.lookup (symtab->compunit ()->objfile (), + symtab); + if (symtab_obj != nullptr) + return (PyObject*)symtab_obj; + } + symtab_obj = PyObject_New (symtab_object, &symtab_object_type); if (symtab_obj) set_symtab (symtab_obj, symtab); @@ -479,14 +395,13 @@ symtab_to_symtab_object (struct symtab *symtab) PyObject * symtab_and_line_to_sal_object (struct symtab_and_line sal) { - gdbpy_ref<sal_object> sal_obj (PyObject_New (sal_object, &sal_object_type)); - if (sal_obj != NULL) - { - if (set_sal (sal_obj.get (), sal) < 0) - return NULL; - } + sal_object *sal_obj; + + sal_obj = PyObject_New (sal_object, &sal_object_type); + if (sal_obj != nullptr) + set_sal (sal_obj, sal); - return (PyObject *) sal_obj.release (); + return (PyObject *) sal_obj; } /* Return struct symtab_and_line reference that is wrapped by this @@ -560,7 +475,7 @@ PyTypeObject symtab_object_type = { "gdb.Symtab", /*tp_name*/ sizeof (symtab_object), /*tp_basicsize*/ 0, /*tp_itemsize*/ - stpy_dealloc, /*tp_dealloc*/ + stpy_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ diff --git a/gdb/python/py-threadevent.c b/gdb/python/py-threadevent.c index 557f2b7..f0a16a1 100644 --- a/gdb/python/py-threadevent.c +++ b/gdb/python/py-threadevent.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2009-2024 Free Software Foundation, Inc. +/* Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c index 09f0a60..23a713e 100644 --- a/gdb/python/py-tui.c +++ b/gdb/python/py-tui.c @@ -1,6 +1,6 @@ /* TUI windows implemented in Python - Copyright (C) 2020-2024 Free Software Foundation, Inc. + Copyright (C) 2020-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c index 11a96d5..c546aa7 100644 --- a/gdb/python/py-type.c +++ b/gdb/python/py-type.c @@ -1,6 +1,6 @@ /* Python interface to types. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -32,12 +32,6 @@ struct type_object { PyObject_HEAD struct type *type; - - /* If a Type object is associated with an objfile, it is kept on a - doubly-linked list, rooted in the objfile. This lets us copy the - underlying struct type when the objfile is deleted. */ - struct type_object *prev; - struct type_object *next; }; extern PyTypeObject type_object_type @@ -164,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 ()); @@ -1162,75 +1156,61 @@ typy_richcompare (PyObject *self, PyObject *other, int op) -/* Deleter that saves types when an objfile is being destroyed. */ -struct typy_deleter +/* Forward declaration, see below. */ +static void set_type (type_object *obj, struct type *type); + +/* Invalidator that saves types when an objfile is being destroyed. */ +struct typy_invalidator { void operator() (type_object *obj) { - if (!gdb_python_initialized) - return; - - /* This prevents another thread from freeing the objects we're - operating on. */ - gdbpy_enter enter_py; - - copied_types_hash_t copied_types; - - while (obj) + if (obj->type->is_objfile_owned ()) { - type_object *next = obj->next; + copied_types_hash_t copied_types; - copied_types.clear (); - obj->type = copy_type_recursive (obj->type, copied_types); - - obj->next = NULL; - obj->prev = NULL; - - obj = next; + /* Set a copied (now arch-owned) type. As a side-effect this + adds OBJ to per-arch list. We do not need to remove it from + per-objfile list since the objfile is going to go completely + anyway. */ + set_type (obj, copy_type_recursive (obj->type, copied_types)); + } + else + { + obj->type = nullptr; } } }; -static const registry<objfile>::key<type_object, typy_deleter> - typy_objfile_data_key; +static const gdbpy_registry<gdbpy_memoizing_registry_storage<type_object, + type, &type_object::type, typy_invalidator>> typy_registry; static void set_type (type_object *obj, struct type *type) { + gdb_assert (type != nullptr); + obj->type = type; - obj->prev = NULL; - if (type != nullptr && type->objfile_owner () != nullptr) - { - struct objfile *objfile = type->objfile_owner (); - obj->next = typy_objfile_data_key.get (objfile); - if (obj->next) - obj->next->prev = obj; - typy_objfile_data_key.set (objfile, obj); - } + if (type->objfile_owner () != nullptr) + typy_registry.add (type->objfile_owner (), obj); else - obj->next = NULL; + typy_registry.add (type->arch_owner (), obj); } static void typy_dealloc (PyObject *obj) { - type_object *type = (type_object *) obj; + type_object *type_obj = (type_object *) obj; - if (type->prev) - type->prev->next = type->next; - else if (type->type != nullptr && type->type->objfile_owner () != nullptr) + if (type_obj->type != nullptr) { - /* Must reset head of list. */ - struct objfile *objfile = type->type->objfile_owner (); - - if (objfile) - typy_objfile_data_key.set (objfile, type->next); + if (type_obj->type->is_objfile_owned ()) + typy_registry.remove (type_obj->type->objfile_owner (), type_obj); + else + typy_registry.remove (type_obj->type->arch_owner (), type_obj); } - if (type->next) - type->next->prev = type->prev; - Py_TYPE (type)->tp_free (type); + Py_TYPE (obj)->tp_free (obj); } /* Return number of fields ("length" of the field dictionary). */ @@ -1473,6 +1453,16 @@ type_to_type_object (struct type *type) return gdbpy_handle_gdb_exception (nullptr, except); } + /* Look if there's already a gdb.Type object for given TYPE + and if so, return it. */ + if (type->is_objfile_owned ()) + type_obj = typy_registry.lookup (type->objfile_owner (), type); + else + type_obj = typy_registry.lookup (type->arch_owner (), type); + + if (type_obj != nullptr) + return (PyObject*)type_obj; + type_obj = PyObject_New (type_object, &type_object_type); if (type_obj) set_type (type_obj, type); @@ -1684,7 +1674,7 @@ PyTypeObject type_object_type = "gdb.Type", /*tp_name*/ sizeof (type_object), /*tp_basicsize*/ 0, /*tp_itemsize*/ - typy_dealloc, /*tp_dealloc*/ + typy_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ diff --git a/gdb/python/py-uiout.h b/gdb/python/py-uiout.h index 5f8c530..f6fbd83 100644 --- a/gdb/python/py-uiout.h +++ b/gdb/python/py-uiout.h @@ -1,6 +1,6 @@ /* Python implementation of ui_out - Copyright (C) 2023-2024 Free Software Foundation, Inc. + Copyright (C) 2023-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c index af8ee98..dc078ec 100644 --- a/gdb/python/py-unwind.c +++ b/gdb/python/py-unwind.c @@ -1,6 +1,6 @@ /* Python frame unwinder interface. - Copyright (C) 2015-2024 Free Software Foundation, Inc. + Copyright (C) 2015-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -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/py-utils.c b/gdb/python/py-utils.c index 0bc18a6..df8b888 100644 --- a/gdb/python/py-utils.c +++ b/gdb/python/py-utils.c @@ -1,6 +1,6 @@ /* General utility routines for GDB/Python. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c index 02c50b4..8a2e263 100644 --- a/gdb/python/py-value.c +++ b/gdb/python/py-value.c @@ -1,6 +1,6 @@ /* Python interface to values. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -60,7 +60,6 @@ struct value_object { struct value_object *prev; struct value *value; PyObject *address; - PyObject *type; PyObject *dynamic_type; PyObject *content_bytes; }; @@ -84,8 +83,6 @@ valpy_clear_value (value_object *self) self->value = nullptr; Py_CLEAR (self->address); - Py_CLEAR (self->type); - Py_CLEAR (self->dynamic_type); Py_CLEAR (self->content_bytes); } @@ -438,14 +435,7 @@ valpy_get_type (PyObject *self, void *closure) { value_object *obj = (value_object *) self; - if (!obj->type) - { - obj->type = type_to_type_object (obj->value->type ()); - if (!obj->type) - return NULL; - } - Py_INCREF (obj->type); - return obj->type; + return type_to_type_object (obj->value->type ()); } /* Return dynamic type of the value. */ @@ -454,13 +444,7 @@ static PyObject * valpy_get_dynamic_type (PyObject *self, void *closure) { value_object *obj = (value_object *) self; - struct type *type = NULL; - - if (obj->dynamic_type != NULL) - { - Py_INCREF (obj->dynamic_type); - return obj->dynamic_type; - } + struct type *type = nullptr; try { @@ -493,23 +477,14 @@ valpy_get_dynamic_type (PyObject *self, void *closure) else if (type->code () == TYPE_CODE_STRUCT) type = value_rtti_type (val, NULL, NULL, NULL); else - { - /* Re-use object's static type. */ - type = NULL; - } + type = val->type (); } catch (const gdb_exception &except) { return gdbpy_handle_gdb_exception (nullptr, except); } - if (type == NULL) - obj->dynamic_type = valpy_get_type (self, NULL); - else - obj->dynamic_type = type_to_type_object (type); - - Py_XINCREF (obj->dynamic_type); - return obj->dynamic_type; + return type_to_type_object (type); } /* Implementation of gdb.Value.lazy_string ([encoding] [, length]) -> @@ -1121,8 +1096,7 @@ valpy_getitem (PyObject *self, PyObject *key) res_val = value_struct_elt (&tmp, {}, field.get (), NULL, "struct/class/union"); else if (bitpos >= 0) - res_val = value_struct_elt_bitpos (&tmp, bitpos, field_type, - "struct/class/union"); + res_val = value_struct_elt_bitpos (tmp, bitpos, field_type); else if (base_class_type != NULL) { struct type *val_type; @@ -1937,15 +1911,14 @@ value_to_value_object (struct value *val) value_object *val_obj; val_obj = PyObject_New (value_object, &value_object_type); - if (val_obj != NULL) + if (val_obj != nullptr) { val->incref (); val_obj->value = val; val_obj->next = nullptr; val_obj->prev = nullptr; - val_obj->address = NULL; - val_obj->type = NULL; - val_obj->dynamic_type = NULL; + val_obj->address = nullptr; + val_obj->dynamic_type = nullptr; val_obj->content_bytes = nullptr; note_value (val_obj); } diff --git a/gdb/python/py-varobj.c b/gdb/python/py-varobj.c index cbfea26..2d36091 100644 --- a/gdb/python/py-varobj.c +++ b/gdb/python/py-varobj.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2013-2024 Free Software Foundation, Inc. +/* Copyright (C) 2013-2025 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/gdb/python/py-xmethods.c b/gdb/python/py-xmethods.c index 2fae043..deac98f 100644 --- a/gdb/python/py-xmethods.c +++ b/gdb/python/py-xmethods.c @@ -1,6 +1,6 @@ /* Support for debug methods in Python. - Copyright (C) 2013-2024 Free Software Foundation, Inc. + Copyright (C) 2013-2025 Free Software Foundation, Inc. This file is part of GDB. diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index c48f260..7f4237e 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1,6 +1,6 @@ /* Gdb/Python header for private use by Python module. - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -22,6 +22,7 @@ #include "extension.h" #include "extension-priv.h" +#include "registry.h" /* These WITH_* macros are defined by the CPython API checker that comes with the Python plugin for GCC. See: @@ -87,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 @@ -134,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> @@ -518,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); @@ -1145,4 +1138,198 @@ gdbpy_type_ready (PyTypeObject *type, PyObject *mod = nullptr) # define PyType_Ready POISONED_PyType_Ready #endif +/* A class to manage lifecycle of Python objects for objects that are "owned" + by an objfile or a gdbarch. It keeps track of Python objects and when + the "owning" object (objfile or gdbarch) is about to be freed, ensures that + all Python objects "owned" by that object are properly invalidated. + + The actual tracking of "owned" Python objects is handled externally + by storage class. Storage object is created for each owning object + on demand and it is deleted when owning object is about to be freed. + + The storage class must provide two member types: + + * obj_type - the type of Python object whose lifecycle is managed. + * val_type - the type of GDB structure the Python objects are + representing. + + It must also provide following methods: + + void add (obj_type *obj); + void remove (obj_type *obj); + + Memoizing storage must in addition to method above provide: + + obj_type *lookup (val_type *val); + + Finally it must invalidate all registered Python objects upon deletion. */ +template <typename Storage> +class gdbpy_registry +{ +public: + using obj_type = typename Storage::obj_type; + using val_type = typename Storage::val_type; + + /* Register Python object OBJ as being "owned" by OWNER. When OWNER is + about to be freed, OBJ will be invalidated. */ + template <typename O> + void add (O *owner, obj_type *obj) const + { + get_storage (owner)->add (obj); + } + + /* Unregister Python object OBJ. OBJ will no longer be invalidated when + OWNER is about to be be freed. */ + template <typename O> + void remove (O *owner, obj_type *obj) const + { + get_storage (owner)->remove (obj); + } + + /* Lookup pre-existing Python object for given VAL. Return such object + if found, otherwise return NULL. This method always returns new + reference. */ + template <typename O> + obj_type *lookup (O *owner, val_type *val) const + { + obj_type *obj = get_storage (owner)->lookup (val); + Py_XINCREF (obj); + return obj; + } + +private: + + template<typename O> + using StorageKey = typename registry<O>::template key<Storage>; + + template<typename O> + Storage *get_storage (O *owner, const StorageKey<O> &key) const + { + Storage *r = key.get (owner); + if (r == nullptr) + { + r = new Storage(); + key.set (owner, r); + } + return r; + } + + Storage *get_storage (struct objfile* objf) const + { + return get_storage (objf, m_key_for_objf); + } + + Storage *get_storage (struct gdbarch* arch) const + { + return get_storage (arch, m_key_for_arch); + } + + const registry<objfile>::key<Storage> m_key_for_objf; + const registry<gdbarch>::key<Storage> m_key_for_arch; +}; + +/* Default invalidator for Python objects. */ +template <typename P, typename V, V* P::*val_slot> +struct gdbpy_default_invalidator +{ + void operator() (P *obj) + { + obj->*val_slot = nullptr; + } +}; + +/* A "storage" implementation suitable for temporary (on-demand) objects. */ +template <typename P, + typename V, + V* P::*val_slot, + typename Invalidator = gdbpy_default_invalidator<P, V, val_slot>> +class gdbpy_tracking_registry_storage +{ +public: + using obj_type = P; + using val_type = V; + + void add (obj_type *obj) + { + gdb_assert (obj != nullptr && obj->*val_slot != nullptr); + + m_objects.insert (obj); + } + + void remove (obj_type *obj) + { + gdb_assert (obj != nullptr && obj->*val_slot != nullptr); + gdb_assert (m_objects.contains (obj)); + + m_objects.erase (obj); + } + + ~gdbpy_tracking_registry_storage () + { + Invalidator invalidate; + gdbpy_enter enter_py; + + for (auto each : m_objects) + invalidate (each); + m_objects.clear (); + } + +protected: + gdb::unordered_set<obj_type *> m_objects; +}; + +/* A "storage" implementation suitable for memoized (interned) Python objects. + + Python objects are memoized (interned) temporarily, meaning that when user + drops all their references the Python object is deallocated and removed + from storage. + */ +template <typename P, + typename V, + V* P::*val_slot, + typename Invalidator = gdbpy_default_invalidator<P, V, val_slot>> +class gdbpy_memoizing_registry_storage +{ +public: + using obj_type = P; + using val_type = V; + + void add (obj_type *obj) + { + gdb_assert (obj != nullptr && obj->*val_slot != nullptr); + + m_objects[obj->*val_slot] = obj; + } + + void remove (obj_type *obj) + { + gdb_assert (obj != nullptr && obj->*val_slot != nullptr); + gdb_assert (m_objects.contains (obj->*val_slot)); + + m_objects.erase (obj->*val_slot); + } + + obj_type *lookup (val_type *val) const + { + auto result = m_objects.find (val); + if (result != m_objects.end ()) + return result->second; + else + return nullptr; + } + + ~gdbpy_memoizing_registry_storage () + { + Invalidator invalidate; + gdbpy_enter enter_py; + + for (auto each : m_objects) + invalidate (each.second); + m_objects.clear (); + } + +protected: + gdb::unordered_map<val_type *, obj_type *> m_objects; +}; + #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */ diff --git a/gdb/python/python.c b/gdb/python/python.c index 94b1fc9..cb0d642 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1,6 +1,6 @@ /* General python/gdb code - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -36,6 +36,7 @@ #include "run-on-main-thread.h" #include "observable.h" #include "build-id.h" +#include "cli/cli-style.h" #if GDB_SELF_TEST #include "gdbsupport/selftest.h" @@ -127,7 +128,8 @@ static bool gdbpy_check_quit_flag (const struct extension_language_defn *); static enum ext_lang_rc gdbpy_before_prompt_hook (const struct extension_language_defn *, const char *current_gdb_prompt); static std::optional<std::string> gdbpy_colorize - (const std::string &filename, const std::string &contents); + (const std::string &filename, const std::string &contents, + enum language lang); static std::optional<std::string> gdbpy_colorize_disasm (const std::string &content, gdbarch *gdbarch); static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo @@ -660,12 +662,14 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw) const char *arg; PyObject *from_tty_obj = nullptr; PyObject *to_string_obj = nullptr; - static const char *keywords[] = { "command", "from_tty", "to_string", - nullptr }; + PyObject *styling = nullptr; + static const char *keywords[] + = { "command", "from_tty", "to_string", "styling", nullptr }; - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|O!O!", keywords, &arg, + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|O!O!O!", keywords, &arg, &PyBool_Type, &from_tty_obj, - &PyBool_Type, &to_string_obj)) + &PyBool_Type, &to_string_obj, + &PyBool_Type, &styling)) return nullptr; bool from_tty = false; @@ -686,6 +690,15 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw) to_string = (cmp != 0); } + bool styling_p = !to_string; + if (styling != nullptr) + { + int cmp = PyObject_IsTrue (styling); + if (cmp < 0) + return nullptr; + styling_p = (cmp != 0); + } + std::string to_string_res; scoped_restore preventer = prevent_dont_repeat (); @@ -745,14 +758,29 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw) scoped_restore save_uiout = make_scoped_restore (¤t_uiout); + /* If the Python 'styling' argument was False then temporarily + disable styling. Otherwise, don't do anything, styling could + already be disabled for some other reason, we shouldn't override + that and force styling on. */ + std::optional<scoped_disable_styling> disable_styling; + if (!styling_p) + disable_styling.emplace (); + /* Use the console interpreter uiout to have the same print format for console or MI. */ interp = interp_lookup (current_ui, "console"); current_uiout = interp->interp_ui_out (); if (to_string) - to_string_res = execute_control_commands_to_string (lines.get (), - from_tty); + { + /* Pass 'true' here to always request styling, however, if + the scoped_disable_styling disabled styling, or the user + has globally disabled styling, then the output will not be + styled. */ + to_string_res + = execute_control_commands_to_string (lines.get (), from_tty, + true); + } else execute_control_commands (lines.get (), from_tty); } @@ -772,7 +800,8 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw) } if (to_string) - return PyUnicode_FromString (to_string_res.c_str ()); + return PyUnicode_Decode (to_string_res.c_str (), to_string_res.size (), + host_charset (), nullptr); Py_RETURN_NONE; } @@ -1267,7 +1296,8 @@ gdbpy_before_prompt_hook (const struct extension_language_defn *extlang, /* This is the extension_language_ops.colorize "method". */ static std::optional<std::string> -gdbpy_colorize (const std::string &filename, const std::string &contents) +gdbpy_colorize (const std::string &filename, const std::string &contents, + enum language lang) { if (!gdb_python_initialized) return {}; @@ -1301,6 +1331,13 @@ gdbpy_colorize (const std::string &filename, const std::string &contents) return {}; } + gdbpy_ref<> lang_arg (PyUnicode_FromString (language_str (lang))); + if (lang_arg == nullptr) + { + gdbpy_print_stack (); + return {}; + } + /* The pygments library, which is what we currently use for applying styling, is happy to take input as a bytes object, and to figure out the encoding for itself. This removes the need for us to figure out @@ -1321,6 +1358,7 @@ gdbpy_colorize (const std::string &filename, const std::string &contents) gdbpy_ref<> result (PyObject_CallFunctionObjArgs (hook.get (), fname_arg.get (), contents_arg.get (), + lang_arg.get (), nullptr)); if (result == nullptr) { @@ -1574,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; @@ -2405,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)); { @@ -2634,7 +2709,7 @@ test_python () #undef CHECK_OUTPUT -} // namespace selftests +} /* namespace selftests */ #endif /* GDB_SELF_TEST */ #endif /* HAVE_PYTHON */ @@ -2642,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, @@ -3083,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} }; diff --git a/gdb/python/python.h b/gdb/python/python.h index e803285..f564af7 100644 --- a/gdb/python/python.h +++ b/gdb/python/python.h @@ -1,6 +1,6 @@ /* Python/gdb header for generic use in gdb - Copyright (C) 2008-2024 Free Software Foundation, Inc. + Copyright (C) 2008-2025 Free Software Foundation, Inc. This file is part of GDB. |