diff options
Diffstat (limited to 'gdb/python')
119 files changed, 3768 insertions, 1763 deletions
diff --git a/gdb/python/lib/gdb/FrameDecorator.py b/gdb/python/lib/gdb/FrameDecorator.py index 82412de..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.""" @@ -285,6 +285,9 @@ class FrameVars(object): # returns False for arguments as well. Anyway, # don't include non-variables here. continue + elif sym.is_artificial: + # Skip artificial symbols. + continue lvars.append(SymValueWrapper(frame, sym)) if block.function is not None: 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 6c3e241..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 @@ -87,8 +92,9 @@ xmethods = [] frame_filters = {} # Initial frame unwinders. frame_unwinders = [] -# Initial missing debug handlers. -missing_debug_handlers = [] +# The missing file handlers. Each item is a tuple with the form +# (TYPE, HANDLER) where TYPE is a string either 'debug' or 'objfile'. +missing_file_handlers = [] def _execute_unwinders(pending_frame): @@ -188,7 +194,7 @@ def GdbSetPythonDirectory(dir): def current_progspace(): "Return the current Progspace." - return _gdb.selected_inferior().progspace + return selected_inferior().progspace def objfiles(): @@ -225,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. @@ -271,6 +277,61 @@ class Thread(threading.Thread): super().start() +def _filter_missing_file_handlers(handlers, handler_type): + """Each list of missing file handlers is a list of tuples, the first + item in the tuple is a string either 'debug' or 'objfile' to + indicate what type of handler it is. The second item in the tuple + is the actual handler object. + + This function takes HANDLER_TYPE which is a string, either 'debug' + or 'objfile' and HANDLERS, a list of tuples. The function returns + an iterable over all of the handler objects (extracted from the + tuples) which match HANDLER_TYPE. + """ + + return map(lambda t: t[1], filter(lambda t: t[0] == handler_type, handlers)) + + +def _handle_missing_files(pspace, handler_type, cb): + """Helper for _handle_missing_debuginfo and _handle_missing_objfile. + + Arguments: + pspace: The gdb.Progspace in which we're operating. Used to + lookup program space specific handlers. + handler_type: A string, either 'debug' or 'objfile', this is the + type of handler we're looking for. + cb: A callback which takes a handler and returns the result of + calling the handler. + + Returns: + None: No suitable file could be found. + False: A handler has decided that the requested file cannot be + found, and no further searching should be done. + True: The file has been found and installed in a location + where GDB would normally look for it. GDB should + repeat its lookup process, the file should now be in + place. + A string: This is the filename of where the missing file can + be found. + """ + + for handler in _filter_missing_file_handlers( + pspace.missing_file_handlers, handler_type + ): + if handler.enabled: + result = cb(handler) + if result is not None: + return result + + for handler in _filter_missing_file_handlers(missing_file_handlers, handler_type): + if handler.enabled: + result = cb(handler) + if result is not None: + return result + + return None + + def _handle_missing_debuginfo(objfile): """Internal function called from GDB to execute missing debug handlers. @@ -293,18 +354,164 @@ def _handle_missing_debuginfo(objfile): A string: This is the filename of a file containing the required debug information. """ + pspace = objfile.progspace - for handler in pspace.missing_debug_handlers: - if handler.enabled: - result = handler(objfile) - if result is not None: - return result + return _handle_missing_files(pspace, "debug", lambda h: h(objfile)) - for handler in missing_debug_handlers: - if handler.enabled: - result = handler(objfile) - if result is not None: - return result - return None +def _handle_missing_objfile(pspace, buildid, filename): + """Internal function called from GDB to execute missing objfile + handlers. + + Run each of the currently registered, and enabled missing objfile + handler objects for the gdb.Progspace passed in as an argument, + and then from the global list. Stop after the first handler that + returns a result other than None. + + Arguments: + pspace: A gdb.Progspace for which the missing objfile handlers + should be run. This is the program space in which an + objfile was found to be missing. + buildid: A string containing the build-id we're looking for. + filename: The filename of the file GDB tried to find but + couldn't. This is not where the file should be + placed if found, in fact, this file might already + exist on disk but have the wrong build-id. This is + mostly provided in order to be used in messages to + the user. + + Returns: + None: No objfile could be found for this build-id. + False: A handler has done all it can with for this build-id, + but no objfile could be found. + True: An objfile might have been installed by a handler, GDB + should check again. The only place GDB checks is within + the .build-id sub-directory within the + debug-file-directory. If the required file was not + installed there then GDB will not find it. + A string: This is the filename of a file containing the + missing objfile. + """ + + 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_debug.py b/gdb/python/lib/gdb/command/missing_files.py index 313b88c..09d9684 100644 --- a/gdb/python/lib/gdb/command/missing_debug.py +++ b/gdb/python/lib/gdb/command/missing_files.py @@ -1,6 +1,6 @@ -# Missing debug related commands. +# 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 @@ -21,7 +21,7 @@ import gdb def validate_regexp(exp, idstring): - """Compile exp into a compiler regular expression object. + """Compile exp into a compiled regular expression object. Arguments: exp: The string to compile into a re.Pattern object. @@ -33,14 +33,15 @@ def validate_regexp(exp, idstring): Raises: SyntaxError: If exp is an invalid regexp. """ + try: return re.compile(exp) except SyntaxError: raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp)) -def parse_missing_debug_command_args(arg): - """Internal utility to parse missing debug handler command argv. +def parse_missing_file_command_args(arg): + """Internal utility to parse missing file handler command argv. Arguments: arg: The arguments to the command. The format is: @@ -52,6 +53,7 @@ def parse_missing_debug_command_args(arg): Raises: SyntaxError: an error processing ARG """ + argv = gdb.string_to_argv(arg) argc = len(argv) if argc > 2: @@ -68,10 +70,10 @@ def parse_missing_debug_command_args(arg): ) -class InfoMissingDebugHanders(gdb.Command): - """GDB command to list missing debug handlers. +class InfoMissingFileHandlers(gdb.Command): + """GDB command to list missing HTYPE handlers. - Usage: info missing-debug-handlers [LOCUS-REGEXP [NAME-REGEXP]] + Usage: info missing-HTYPE-handlers [LOCUS-REGEXP [NAME-REGEXP]] LOCUS-REGEXP is a regular expression matching the location of the handler. If it is omitted, all registered handlers from all @@ -79,38 +81,47 @@ class InfoMissingDebugHanders(gdb.Command): the handlers from the current progspace, or a regular expression matching filenames of progspaces. - NAME-REGEXP is a regular expression to filter missing debug + NAME-REGEXP is a regular expression to filter missing HTYPE handler names. If this omitted for a specified locus, then all registered handlers in the locus are listed. """ - def __init__(self): - super().__init__("info missing-debug-handlers", gdb.COMMAND_FILES) + def __init__(self, handler_type): + # Update the doc string before calling the parent constructor, + # replacing the string 'HTYPE' with the value of HANDLER_TYPE. + # The parent constructor will grab a copy of this string to + # use as the commands help text. + self.__doc__ = self.__doc__.replace("HTYPE", handler_type) + super().__init__( + "info missing-" + handler_type + "-handlers", gdb.COMMAND_FILES + ) + self.handler_type = handler_type def list_handlers(self, title, handlers, name_re): - """Lists the missing debug handlers whose name matches regexp. + """Lists the missing file handlers whose name matches regexp. Arguments: title: The line to print before the list. - handlers: The list of the missing debug handlers. + handlers: The list of the missing file handlers. name_re: handler name filter. """ + if not handlers: return print(title) - for handler in handlers: + for handler in gdb._filter_missing_file_handlers(handlers, self.handler_type): if name_re.match(handler.name): print( " %s%s" % (handler.name, "" if handler.enabled else " [disabled]") ) def invoke(self, arg, from_tty): - locus_re, name_re = parse_missing_debug_command_args(arg) + locus_re, name_re = parse_missing_file_command_args(arg) if locus_re.match("progspace") and locus_re.pattern != "": cp = gdb.current_progspace() self.list_handlers( - "Progspace %s:" % cp.filename, cp.missing_debug_handlers, name_re + "Progspace %s:" % cp.filename, cp.missing_file_handlers, name_re ) for progspace in gdb.progspaces(): @@ -125,58 +136,71 @@ class InfoMissingDebugHanders(gdb.Command): msg = "Progspace %s:" % filename self.list_handlers( msg, - progspace.missing_debug_handlers, + progspace.missing_file_handlers, name_re, ) # Print global handlers last, as these are invoked last. if locus_re.match("global"): - self.list_handlers("Global:", gdb.missing_debug_handlers, name_re) + self.list_handlers("Global:", gdb.missing_file_handlers, name_re) -def do_enable_handler1(handlers, name_re, flag): - """Enable/disable missing debug handlers whose names match given regex. +def do_enable_handler1(handlers, name_re, flag, handler_type): + """Enable/disable missing file handlers whose names match given regex. Arguments: - handlers: The list of missing debug handlers. + handlers: The list of missing file handlers. name_re: Handler name filter. flag: A boolean indicating if we should enable or disable. + handler_type: A string, either 'debug' or 'objfile', use to control + which handlers are modified. Returns: The number of handlers affected. """ + total = 0 - for handler in handlers: + for handler in gdb._filter_missing_file_handlers(handlers, handler_type): if name_re.match(handler.name) and handler.enabled != flag: handler.enabled = flag total += 1 return total -def do_enable_handler(arg, flag): - """Enable or disable missing debug handlers.""" - (locus_re, name_re) = parse_missing_debug_command_args(arg) +def do_enable_handler(arg, flag, handler_type): + """Enable or disable missing file handlers.""" + + (locus_re, name_re) = parse_missing_file_command_args(arg) total = 0 if locus_re.match("global"): - total += do_enable_handler1(gdb.missing_debug_handlers, name_re, flag) + total += do_enable_handler1( + gdb.missing_file_handlers, name_re, flag, handler_type + ) if locus_re.match("progspace") and locus_re.pattern != "": total += do_enable_handler1( - gdb.current_progspace().missing_debug_handlers, name_re, flag + gdb.current_progspace().missing_file_handlers, name_re, flag, handler_type ) for progspace in gdb.progspaces(): filename = progspace.filename or "" if locus_re.match(filename): - total += do_enable_handler1(progspace.missing_debug_handlers, name_re, flag) + total += do_enable_handler1( + progspace.missing_file_handlers, name_re, flag, handler_type + ) print( - "%d missing debug handler%s %s" - % (total, "" if total == 1 else "s", "enabled" if flag else "disabled") + "%d missing %s handler%s %s" + % ( + total, + handler_type, + "" if total == 1 else "s", + "enabled" if flag else "disabled", + ) ) -class EnableMissingDebugHandler(gdb.Command): - """GDB command to enable missing debug handlers. +class EnableMissingFileHandler(gdb.Command): + """GDB command to enable missing HTYPE handlers. - Usage: enable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]] + Usage: enable missing-HTYPE-handler [LOCUS-REGEXP [NAME-REGEXP]] LOCUS-REGEXP is a regular expression specifying the handlers to enable. It can be 'global', 'progspace' for the current @@ -187,18 +211,26 @@ class EnableMissingDebugHandler(gdb.Command): in the locus are affected. """ - def __init__(self): - super().__init__("enable missing-debug-handler", gdb.COMMAND_FILES) + def __init__(self, handler_type): + # Update the doc string before calling the parent constructor, + # replacing the string 'HTYPE' with the value of HANDLER_TYPE. + # The parent constructor will grab a copy of this string to + # use as the commands help text. + self.__doc__ = self.__doc__.replace("HTYPE", handler_type) + super().__init__( + "enable missing-" + handler_type + "-handler", gdb.COMMAND_FILES + ) + self.handler_type = handler_type def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" - do_enable_handler(arg, True) + do_enable_handler(arg, True, self.handler_type) -class DisableMissingDebugHandler(gdb.Command): - """GDB command to disable missing debug handlers. +class DisableMissingFileHandler(gdb.Command): + """GDB command to disable missing HTYPE handlers. - Usage: disable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]] + Usage: disable missing-HTYPE-handler [LOCUS-REGEXP [NAME-REGEXP]] LOCUS-REGEXP is a regular expression specifying the handlers to enable. It can be 'global', 'progspace' for the current @@ -209,19 +241,28 @@ class DisableMissingDebugHandler(gdb.Command): in the locus are affected. """ - def __init__(self): - super().__init__("disable missing-debug-handler", gdb.COMMAND_FILES) + def __init__(self, handler_type): + # Update the doc string before calling the parent constructor, + # replacing the string 'HTYPE' with the value of HANDLER_TYPE. + # The parent constructor will grab a copy of this string to + # use as the commands help text. + self.__doc__ = self.__doc__.replace("HTYPE", handler_type) + super().__init__( + "disable missing-" + handler_type + "-handler", gdb.COMMAND_FILES + ) + self.handler_type = handler_type def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" - do_enable_handler(arg, False) + do_enable_handler(arg, False, self.handler_type) -def register_missing_debug_handler_commands(): - """Installs the missing debug handler commands.""" - InfoMissingDebugHanders() - EnableMissingDebugHandler() - DisableMissingDebugHandler() +def register_missing_file_handler_commands(): + """Installs the missing file handler commands.""" + for handler_type in ["debug", "objfile"]: + InfoMissingFileHandlers(handler_type) + EnableMissingFileHandler(handler_type) + DisableMissingFileHandler(handler_type) -register_missing_debug_handler_commands() +register_missing_file_handler_commands() 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 51b9546..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 @@ -92,5 +93,7 @@ def pre_command_loop(): # session. session_started = True startup.thread_log("starting DAP server") - global server + # These are handy for bug reports. + startup.exec_and_log("show version") + startup.exec_and_log("show configuration") 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 e60265b..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 @@ -21,9 +21,16 @@ from typing import Optional, Sequence import gdb -from .server import capability, request, send_event +from .server import capability, export_line, import_line, request, send_event from .sources import make_source -from .startup import DAPException, LogLevel, in_gdb_thread, log_stack, parse_and_eval +from .startup import ( + DAPException, + LogLevel, + exec_mi_and_log, + in_gdb_thread, + log_stack, + parse_and_eval, +) from .typecheck import type_check # True when suppressing new breakpoint events. @@ -44,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", @@ -57,7 +63,6 @@ def _bp_modified(event): @in_gdb_thread def _bp_created(event): - global _suppress_bp if not _suppress_bp: send_event( "breakpoint", @@ -70,7 +75,6 @@ def _bp_created(event): @in_gdb_thread def _bp_deleted(event): - global _suppress_bp if not _suppress_bp: send_event( "breakpoint", @@ -97,11 +101,16 @@ breakpoint_map = {} @in_gdb_thread def _breakpoint_descriptor(bp): "Return the Breakpoint object descriptor given a gdb Breakpoint." + # If there are no objfiles (that is, before the launch request), + # we consider all breakpoints to be pending. This is done to work + # around the gdb oddity that setting a breakpoint by address will + # always succeed. + pending = bp.pending or len(gdb.objfiles()) == 0 result = { "id": bp.number, - "verified": not bp.pending, + "verified": not pending, } - if bp.pending: + if pending: result["reason"] = "pending" if bp.locations: # Just choose the first location, because DAP doesn't allow @@ -116,7 +125,7 @@ def _breakpoint_descriptor(bp): result.update( { "source": make_source(filename), - "line": line, + "line": export_line(line), } ) @@ -139,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] @@ -196,9 +204,9 @@ def _set_breakpoints_callback(kind, specs, creator): } ) - # Delete any breakpoints that were not reused. - for entry in saved_map.values(): - entry.delete() + # Delete any breakpoints that were not reused. + for entry in saved_map.values(): + entry.delete() return result @@ -206,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 @@ -269,14 +277,14 @@ def _rewrite_src_breakpoint( ): return { "source": source["path"], - "line": line, + "line": import_line(line), "condition": condition, "hitCondition": hitCondition, "logMessage": logMessage, } -@request("setBreakpoints") +@request("setBreakpoints", expect_stopped=False) @capability("supportsHitConditionalBreakpoints") @capability("supportsConditionalBreakpoints") @capability("supportsLogPoints") @@ -318,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] @@ -351,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 @@ -368,10 +376,13 @@ def _catch_exception(filterId, **args): cmd = "-catch-" + filterId else: raise DAPException("Invalid exception filterID: " + str(filterId)) - result = gdb.execute_mi(cmd) + result = exec_mi_and_log(cmd) + # While the Ada catchpoints emit a "bkptno" field here, the C++ + # ones do not. So, instead we look at the "number" field. + num = result["bkpt"]["number"] # A little lame that there's no more direct way. for bp in gdb.breakpoints(): - if bp.number == result["bkptno"]: + if bp.number == num: return bp # Not a DAPException because this is definitely unexpected. raise Exception("Could not find catchpoint after creating") @@ -399,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 668bcc7..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 @@ -21,7 +21,7 @@ import gdb from .frames import dap_frame_generator from .modules import module_id from .scopes import symbol_value -from .server import capability, request +from .server import capability, export_line, request from .sources import make_source from .startup import in_gdb_thread from .state import set_thread @@ -86,8 +86,11 @@ def _backtrace(thread_id, levels, startFrame, stack_format): } line = current_frame.line() if line is not None: - newframe["line"] = line + newframe["line"] = export_line(line) if stack_format["line"]: + # Unclear whether export_line should be called + # here, but since it's just for users we pick the + # gdb representation. name += ", line " + str(line) objfile = gdb.current_progspace().objfile_for_address(pc) if objfile is not None: 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 a2e27e5..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 @@ -15,7 +15,7 @@ import gdb -from .server import capability, request +from .server import capability, export_line, request from .sources import make_source @@ -26,34 +26,34 @@ 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: - result["line"] = sal.line + result["line"] = export_line(sal.line) if sal.symtab.filename is not None: # The spec says this can be omitted in some # situations, but it's a little simpler to just always 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 07a4e3e..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 @@ -14,11 +14,13 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import itertools +from typing import Dict import gdb from gdb.frames import frame_iterator from .startup import in_gdb_thread +from .state import set_thread # A list of all the frames we've reported. A frame's index in the # list is its ID. We don't use a hash here because frames are not @@ -29,6 +31,9 @@ _all_frames = [] # Map from a global thread ID to a memoizing frame iterator. _iter_map = {} +# Map from a global frame ID to a thread ID. +thread_ids: Dict[int, int] = {} + # Clear all the frame IDs. @in_gdb_thread @@ -37,6 +42,8 @@ def _clear_frame_ids(evt): _all_frames = [] global _iter_map _iter_map = {} + global thread_ids + thread_ids = {} # Clear the frame ID map whenever the inferior runs. @@ -46,7 +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 _all_frames + if id in thread_ids: + thread_id = thread_ids[id] + if thread_id != gdb.selected_thread().global_num: + set_thread(thread_id) + return _all_frames[id] @@ -63,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 @@ -91,9 +102,9 @@ 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) + thread_ids[num] = gdb.selected_thread().global_num return num def yield_frames(iterator, for_elided): @@ -114,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 149c9a8..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,12 @@ 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): - return self.var_list[idx].value() + sym = self._var_list[idx] + return (sym.name, sym.value()) @in_gdb_thread @@ -77,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] @@ -85,7 +85,7 @@ def get_global_scope(frame): syms = [] block_iter = block while block_iter is not None: - syms += [sym for sym in block_iter if sym.is_variable] + syms += [sym for sym in block_iter if sym.is_variable and not sym.is_artificial] block_iter = block_iter.superblock if len(syms) == 0: 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 2674e02..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 @@ -13,20 +13,65 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import re + # These are deprecated in 3.9, but required in older versions. from typing import Mapping, Optional, Sequence import gdb from .events import exec_and_expect_stop, expect_process, expect_stop -from .server import capability, request -from .startup import DAPException, exec_and_log +from .server import ( + DeferredRequest, + call_function_later, + capability, + request, + send_gdb, + send_gdb_with_response, +) +from .startup import DAPException, exec_and_log, in_dap_thread, in_gdb_thread + +# A launch or attach promise that that will be fulfilled after a +# configurationDone request has been processed. +_launch_or_attach_promise = None + + +# A DeferredRequest that handles either a "launch" or "attach" +# request. +class _LaunchOrAttachDeferredRequest(DeferredRequest): + def __init__(self, callback): + self._callback = callback + global _launch_or_attach_promise + if _launch_or_attach_promise is not None: + raise DAPException("launch or attach already specified") + _launch_or_attach_promise = self + + # Invoke the callback and return the result. + @in_dap_thread + def invoke(self): + return self._callback() + + # Override this so we can clear the global when rescheduling. + @in_dap_thread + def reschedule(self): + global _launch_or_attach_promise + _launch_or_attach_promise = None + super().reschedule() + + +# A wrapper for the 'file' command that correctly quotes its argument. +@in_gdb_thread +def file_command(program): + # Handle whitespace, quotes, and backslashes here. Exactly what + # to quote depends on libiberty's buildargv and safe-ctype. + program = re.sub("[ \t\n\r\f\v\\\\'\"]", "\\\\\\g<0>", program) + exec_and_log("file " + program) # Any parameters here are necessarily extensions -- DAP requires this # from implementations. Any additions or changes here should be # documented in the gdb manual. -@request("launch", response=False) +@request("launch", on_dap_thread=True) def launch( *, program: Optional[str] = None, @@ -34,27 +79,54 @@ def launch( args: Sequence[str] = (), env: Optional[Mapping[str, str]] = None, stopAtBeginningOfMainSubprogram: bool = False, + stopOnEntry: bool = False, **extra, ): - if cwd is not None: - exec_and_log("cd " + cwd) - if program is not None: - exec_and_log("file " + program) - inf = gdb.selected_inferior() - if stopAtBeginningOfMainSubprogram: - main = inf.main_name - if main is not None: - exec_and_log("tbreak " + main) - inf.arguments = args - if env is not None: - inf.clear_env() - for name, value in env.items(): - inf.set_env(name, value) - expect_process("process") - exec_and_expect_stop("run") - - -@request("attach") + # Launch setup is handled here. This is done synchronously so + # that errors can be reported in a natural way. + @in_gdb_thread + def _setup_launch(): + if cwd is not None: + exec_and_log("cd " + cwd) + if program is not None: + file_command(program) + inf = gdb.selected_inferior() + inf.arguments = args + if env is not None: + inf.clear_env() + for name, value in env.items(): + inf.set_env(name, value) + + # Actual launching done here. See below for more info. + @in_gdb_thread + def _do_launch(): + expect_process("process") + if stopAtBeginningOfMainSubprogram: + cmd = "start" + elif stopOnEntry: + cmd = "starti" + else: + cmd = "run" + exec_and_expect_stop(cmd) + + @in_dap_thread + def _launch_impl(): + send_gdb_with_response(_setup_launch) + # We do not wait for the result here. It might be a little + # nicer if we did -- perhaps the various thread events would + # occur in a more logical sequence -- but if the inferior does + # not stop, then the launch response will not be seen either, + # which seems worse. + send_gdb(_do_launch) + # Launch response does not have a body. + return None + + # The launch itself is deferred until the configurationDone + # request. + return _LaunchOrAttachDeferredRequest(_launch_impl) + + +@request("attach", on_dap_thread=True) def attach( *, program: Optional[str] = None, @@ -62,21 +134,38 @@ def attach( target: Optional[str] = None, **args, ): - if program is not None: - exec_and_log("file " + program) - if pid is not None: - cmd = "attach " + str(pid) - elif target is not None: - cmd = "target remote " + target - else: - raise DAPException("attach requires either 'pid' or 'target'") - expect_process("attach") - expect_stop("attach") - exec_and_log(cmd) + # The actual attach is handled by this function. + @in_gdb_thread + def _do_attach(): + if program is not None: + file_command(program) + if pid is not None: + cmd = "attach " + str(pid) + elif target is not None: + cmd = "target remote " + target + else: + raise DAPException("attach requires either 'pid' or 'target'") + expect_process("attach") + expect_stop("attach") + exec_and_log(cmd) + # Attach response does not have a body. + return None + + @in_dap_thread + def _attach_impl(): + return send_gdb_with_response(_do_attach) + + # The attach itself is deferred until the configurationDone + # request. + return _LaunchOrAttachDeferredRequest(_attach_impl) @capability("supportsConfigurationDoneRequest") -@request("configurationDone") +@request("configurationDone", on_dap_thread=True) def config_done(**args): - # Nothing to do. - return None + # Handle the launch or attach. + if _launch_or_attach_promise is None: + raise DAPException("launch or attach not specified") + # Resolve the launch or attach, but only after the + # configurationDone response has been sent. + call_function_later(_launch_or_attach_promise.reschedule) diff --git a/gdb/python/lib/gdb/dap/locations.py b/gdb/python/lib/gdb/dap/locations.py index 92e68f5..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 @@ -16,10 +16,9 @@ # This is deprecated in 3.9, but required in older versions. from typing import Optional -import gdb - -from .server import capability, request +from .server import capability, export_line, import_line, request from .sources import decode_source +from .startup import exec_mi_and_log # Note that the spec says that the arguments to this are optional. @@ -29,15 +28,18 @@ from .sources import decode_source # This points out that fixing this would be an incompatibility but # goes on to propose "if arguments property is missing, debug adapters # should return an error". -@request("breakpointLocations") +@request("breakpointLocations", expect_stopped=False) @capability("supportsBreakpointLocationsRequest") def breakpoint_locations(*, source, line: int, endLine: Optional[int] = None, **extra): + line = import_line(line) if endLine is None: endLine = line + else: + endLine = import_line(endLine) filename = decode_source(source) lines = set() - for entry in gdb.execute_mi("-symbol-list-lines", filename)["lines"]: + for entry in exec_mi_and_log("-symbol-list-lines", filename)["lines"]: this_line = entry["line"] if this_line >= line and this_line <= endLine: - lines.add(this_line) + lines.add(export_line(this_line)) return {"breakpoints": [{"line": x} for x in sorted(lines)]} 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 d0e9115..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 @@ -17,12 +17,12 @@ import gdb from .frames import frame_for_id from .globalvars import get_global_scope -from .server import request +from .server import export_line, request 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 = {} @@ -76,26 +76,24 @@ def symbol_value(sym, frame): class _ScopeReference(BaseReference): - def __init__(self, name, hint, frame, var_list): + def __init__(self, name, hint, frameId: int, var_list): super().__init__(name) - self.hint = hint - self.frame = frame - self.inf_frame = frame.inferior_frame() - self.func = frame.function() - self.line = frame.line() + 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() - if self.line is not None: - result["line"] = self.line - filename = self.frame.filename() + frame = frame_for_id(self._frameId) + if frame.line() is not None: + result["line"] = export_line(frame.line()) + filename = frame.filename() if filename is not None: result["source"] = make_source(filename) return result @@ -104,47 +102,48 @@ 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], self.frame) + return symbol_value(self._var_list[idx], frame_for_id(self._frameId)) -# A _ScopeReference that prepends the most recent return value. Note -# that this object is only created if such a value actually exists. +# A _ScopeReference that wraps the 'finish' value. Note that this +# object is only created if such a value actually exists. class _FinishScopeReference(_ScopeReference): - def __init__(self, *args): - super().__init__(*args) + def __init__(self, frameId): + super().__init__("Return", "returnValue", frameId, ()) def child_count(self): - return super().child_count() + 1 + return 1 def fetch_one_child(self, idx): - if idx == 0: - global _last_return_value - return ("(return)", _last_return_value) - return super().fetch_one_child(idx - 1) + assert idx == 0 + return ("(return)", _last_return_value) class _RegisterReference(_ScopeReference): - def __init__(self, name, frame): + def __init__(self, name, frameId): super().__init__( - name, "registers", frame, frame.inferior_frame().architecture().registers() + name, + "registers", + frameId, + frame_for_id(frameId).inferior_frame().architecture().registers(), ) @in_gdb_thread def fetch_one_child(self, idx): return ( - self.var_list[idx].name, - self.inf_frame.read_register(self.var_list[idx]), + self._var_list[idx].name, + frame_for_id(self._frameId) + .inferior_frame() + .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: @@ -154,16 +153,16 @@ def scopes(*, frameId: int, **extra): # iterator case. args = tuple(frame.frame_args() or ()) if args: - scopes.append(_ScopeReference("Arguments", "arguments", frame, args)) + scopes.append(_ScopeReference("Arguments", "arguments", frameId, args)) has_return_value = frameId == 0 and _last_return_value is not None # Make sure to handle the None case as well as the empty # iterator case. locs = tuple(frame.frame_locals() or ()) + if locs: + scopes.append(_ScopeReference("Locals", "locals", frameId, locs)) + scopes.append(_RegisterReference("Registers", frameId)) if has_return_value: - scopes.append(_FinishScopeReference("Locals", "locals", frame, locs)) - elif locs: - scopes.append(_ScopeReference("Locals", "locals", frame, locs)) - scopes.append(_RegisterReference("Registers", frame)) + scopes.append(_FinishScopeReference(frameId)) frame_to_scope[frameId] = scopes global_scope = get_global_scope(frame) if global_scope is not None: diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py index 8c6d908..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 @@ -46,6 +46,67 @@ _commands = {} # The global server. _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: + """If a DAP request function returns a deferred request, no + response is sent immediately. + + Instead, request processing continues, with this particular + request remaining un-replied-to. + + Later, when the result is available, the deferred request can be + scheduled. This causes 'invoke' to be called and then the + response to be sent to the client. + + """ + + # This is for internal use by the server. It should not be + # overridden by any subclass. This adds the request ID and the + # result template object to this object. These are then used + # during rescheduling. + def set_request(self, req, result): + self._req = req + 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. + + This will be called from 'reschedule' (and should not be + called elsewhere). It should return the 'body' that will be + sent in the response. None means no 'body' field will be set. + + Subclasses must override this. + + """ + pass + + @in_dap_thread + def reschedule(self): + """Call this to reschedule this deferred request. + + This will call 'invoke' after the appropriate bookkeeping and + will arrange for its result to be reported to the client. + + """ + 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 # request needs the inferior to be stopped, but it is not stopped. @@ -59,21 +120,78 @@ class NotStoppedException(Exception): class CancellationHandler: def __init__(self): # Methods on this class acquire this lock before proceeding. - self.lock = threading.Lock() + # A recursive lock is used to simplify the 'check_cancel' + # callers. + self.lock = threading.RLock() # 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() + + @contextmanager + def current_request(self, req): + """Return a new context manager that registers that request + REQ has started.""" + try: + with self.lock: + self.in_flight_dap_thread = req + # Note we do not call check_cancel here. This is a bit of + # a hack, but it's because the direct callers of this + # aren't prepared for a KeyboardInterrupt. + yield + finally: + with self.lock: + self.in_flight_dap_thread = None + + def defer_request(self, req): + """Indicate that the request REQ has been deferred.""" + with self.lock: + self._deferred_ids.add(req) + + def request_finished(self, req): + """Indicate that the request REQ is finished. + + It doesn't matter whether REQ succeeded or failed, only that + processing for it is done. - def starting(self, req): - """Call at the start of the given request.""" + """ with self.lock: - self.in_flight_dap_thread = req + # Use discard here, not remove, because this is called + # regardless of whether REQ was deferred. + self._deferred_ids.discard(req) - def done(self, req): - """Indicate that the request is done.""" + def check_cancel(self, req): + """Check whether request REQ is cancelled. + If so, raise KeyboardInterrupt.""" with self.lock: - self.in_flight_dap_thread = None + # We want to drop any cancellations that come before REQ, + # but keep ones for any deferred requests that are still + # unresolved. This holds any such requests that were + # popped during the loop. + deferred = [] + try: + # If the request is cancelled, don't execute the region. + 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) + if next_id == req: + raise KeyboardInterrupt() + 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 + # request 18 here will lose the cancellation. + # So, we preserve it. + deferred.append(next_id) + finally: + for x in deferred: + heapq.heappush(self._reqs, x) def cancel(self, req): """Call to cancel a request. @@ -86,12 +204,12 @@ class CancellationHandler: gdb.interrupt() else: # We don't actually ignore the request here, but in - # the 'starting' method. This way we don't have to + # the 'check_cancel' method. This way we don't have to # track as much state. Also, this implementation has # 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): @@ -103,10 +221,7 @@ class CancellationHandler: return try: with self.lock: - # If the request is cancelled, don't execute the region. - while len(self.reqs) > 0 and self.reqs[0] <= req: - if heapq.heappop(self.reqs) == req: - raise KeyboardInterrupt() + self.check_cancel(req) # Request is being handled by the gdb thread. self.in_flight_gdb_thread = req # Execute region. This may be interrupted by gdb.interrupt. @@ -121,45 +236,45 @@ 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_events_lock = threading.Lock() - self.defer_stop_events = False - self.delayed_events = [] + 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 - # Treat PARAMS as a JSON-RPC request and perform its action. - # PARAMS is just a dictionary from the JSON. + # A helper for request processing. REQ is the request ID. RESULT + # is a result "template" -- a dictionary with a few items already + # filled in. This helper calls FN and then fills in the remaining + # parts of RESULT, as needed. If FN returns an ordinary result, + # or if it fails, then the final RESULT is sent as a response to + # the client. However, if FN returns a DeferredRequest, then that + # request is updated (see DeferredRequest.set_request) and no + # response is sent. @in_dap_thread - def _handle_command(self, params): - req = params["seq"] - result = { - "request_seq": req, - "type": "response", - "command": params["command"], - } + def invoke_request(self, req, result, fn): try: - self.canceller.starting(req) - if "arguments" in params: - args = params["arguments"] - else: - args = {} - global _commands - body = _commands[params["command"]](**args) - if body is not None: - result["body"] = body + self.canceller.check_cancel(req) + fn_result = fn() result["success"] = True + if isinstance(fn_result, DeferredRequest): + fn_result.set_request(req, result) + self.canceller.defer_request(req) + # Do not send a response. + return + elif fn_result is not None: + result["body"] = fn_result except NotStoppedException: # This is an expected exception, and the result is clearly # visible in the log, so do not log it. @@ -179,19 +294,39 @@ class Server: log_stack() result["success"] = False result["message"] = str(e) - return result + self.canceller.request_finished(req) + # We have a response for the request, so send it back to the + # client. + self._send_json(result) + + # Treat PARAMS as a JSON-RPC request and perform its action. + # PARAMS is just a dictionary from the JSON. @in_dap_thread - def _handle_command_finish(self, params): + def _handle_command(self, params): req = params["seq"] - self.canceller.done(req) + result = { + "request_seq": req, + "type": "response", + "command": params["command"], + } + + if "arguments" in params: + args = params["arguments"] + else: + args = {} + + def fn(): + return _commands[params["command"]](**args) + + self.invoke_request(req, result, fn) # Read inferior output and sends OutputEvents to the client. It # 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", @@ -202,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 @@ -210,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) + ">>>") @@ -226,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): @@ -236,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 - result = self._handle_command(cmd) - self._send_json(result) - self._handle_command_finish(cmd) - events = None - with self.delayed_events_lock: - events = self.delayed_events - self.delayed_events = [] - self.defer_stop_events = False - for event, body in events: - self.send_event(event, body) + req = cmd["seq"] + with self.canceller.current_request(req): + self._handle_command(cmd) + 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_events_lock: - self.delayed_events.append((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, @@ -275,16 +415,22 @@ class Server: the client.""" with self.canceller.lock: if self.canceller.in_flight_dap_thread: - with self.delayed_events_lock: - if self.defer_stop_events: - self.delayed_events.append((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) # 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.""" @@ -301,24 +447,19 @@ 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) + _server.send_event_maybe_later(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.""" + _server.call_function_later(fn) # A helper decorator that checks whether the inferior is running. @@ -342,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. @@ -364,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. @@ -389,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 @@ -417,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 @@ -430,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 @@ -438,22 +583,24 @@ def capability(name, value=True): return wrap -def client_bool_capability(name): +def client_bool_capability(name, default=False): """Return the value of a boolean client capability. If the capability was not specified, or did not have boolean type, - False is returned.""" - global _server + DEFAULT is returned. DEFAULT defaults to False.""" if name in _server.config and isinstance(_server.config[name], bool): return _server.config[name] - return False + return default @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() @@ -490,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 @@ -511,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 @@ -557,3 +704,39 @@ def send_gdb_with_response(fn): if isinstance(val, (Exception, KeyboardInterrupt)): raise val return val + + +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.""" + 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 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.""" + 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 ad0c913..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 @@ -15,10 +15,8 @@ import os -import gdb - from .server import capability, request -from .startup import DAPException, in_gdb_thread +from .startup import DAPException, exec_mi_and_log, in_gdb_thread # The next available source reference ID. Must be greater than 0. _next_source = 1 @@ -39,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: @@ -55,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 @@ -68,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"] @@ -83,7 +78,7 @@ def decode_source(source): @capability("supportsLoadedSourcesRequest") def loaded_sources(**extra): result = [] - for elt in gdb.execute_mi("-file-list-exec-source-files")["files"]: + for elt in exec_mi_and_log("-file-list-exec-source-files")["files"]: result.append(make_source(elt["fullname"], elt["file"])) return { "sources": result, diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py index 3952447..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 @@ -217,3 +217,10 @@ def exec_and_log(cmd, propagate_exception=False): raise DAPException(str(e)) from e else: log_stack() + + +@in_gdb_thread +def exec_mi_and_log(*args): + """Wrap gdb.execute_mi, logging the command.""" + log("+++ " + str(args)) + return gdb.execute_mi(*args) 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 57e84a1..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 @@ -18,6 +18,7 @@ from collections import defaultdict from contextlib import contextmanager import gdb +import gdb.printing from .server import client_bool_capability from .startup import DAPException, in_gdb_thread @@ -59,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 @@ -70,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 @@ -82,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 @@ -96,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): @@ -127,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 @@ -145,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): @@ -164,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 + "'") @@ -180,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: @@ -196,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 @@ -229,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: @@ -244,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 @@ -268,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 72d311b..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. @@ -147,7 +147,7 @@ class maint_info_py_disassemblers_cmd(gdb.Command): # Figure out the name of the current architecture. There # should always be a current inferior, but if, somehow, there # isn't, then leave curr_arch as the empty string, which will - # not then match agaisnt any architecture in the dictionary. + # not then match against any architecture in the dictionary. curr_arch = "" if gdb.selected_inferior() is not None: curr_arch = gdb.selected_inferior().architecture().name() 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 6d57462..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 @@ -17,48 +17,11 @@ MissingDebugHandler base class, and register_handler function. """ -import sys - import gdb +from gdb.missing_files import MissingFileHandler -if sys.version_info >= (3, 7): - # Functions str.isascii() and str.isalnum are available starting Python - # 3.7. - def isascii(ch): - return ch.isascii() - - def isalnum(ch): - return ch.isalnum() - -else: - # Fall back to curses.ascii.isascii() and curses.ascii.isalnum() for - # earlier versions. - from curses.ascii import isalnum, isascii - - -def _validate_name(name): - """Validate a missing debug handler name string. - - If name is valid as a missing debug handler name, then this - function does nothing. If name is not valid then an exception is - raised. - - Arguments: - name: A string, the name of a missing debug handler. - - Returns: - Nothing. - Raises: - ValueError: If name is invalid as a missing debug handler - name. - """ - for ch in name: - if not isascii(ch) or not (isalnum(ch) or ch in "_-"): - raise ValueError("invalid character '%s' in handler name: %s" % (ch, name)) - - -class MissingDebugHandler(object): +class MissingDebugHandler(MissingFileHandler): """Base class for missing debug handlers written in Python. A missing debug handler has a single method __call__ along with @@ -69,41 +32,8 @@ class MissingDebugHandler(object): enabled: When true this handler is enabled. """ - def __init__(self, name): - """Constructor. - - Args: - name: An identifying name for this handler. - - Raises: - TypeError: name is not a string. - ValueError: name contains invalid characters. - """ - - if not isinstance(name, str): - raise TypeError("incorrect type for name: %s" % type(name)) - - _validate_name(name) - - self._name = name - self._enabled = True - - @property - def name(self): - return self._name - - @property - def enabled(self): - return self._enabled - - @enabled.setter - def enabled(self, value): - if not isinstance(value, bool): - raise TypeError("incorrect type for enabled attribute: %s" % type(value)) - self._enabled = value - def __call__(self, objfile): - """GDB handle missing debug information for an objfile. + """Handle missing debug information for an objfile. Arguments: objfile: A gdb.Objfile for which GDB could not find any @@ -124,62 +54,5 @@ class MissingDebugHandler(object): def register_handler(locus, handler, replace=False): - """Register handler in given locus. - - The handler is prepended to the locus's missing debug handlers - list. The name of handler should be unique (or replace must be - True). - - Arguments: - locus: Either a progspace, or None (in which case the unwinder - is registered globally). - handler: An object of a gdb.MissingDebugHandler subclass. - - replace: If True, replaces existing handler with the same name - within locus. Otherwise, raises RuntimeException if - unwinder with the same name already exists. - - Returns: - Nothing. - - Raises: - RuntimeError: The name of handler is not unique. - TypeError: Bad locus type. - AttributeError: Required attributes of handler are missing. - """ - - if locus is None: - if gdb.parameter("verbose"): - gdb.write("Registering global %s handler ...\n" % handler.name) - locus = gdb - elif isinstance(locus, gdb.Progspace): - if gdb.parameter("verbose"): - gdb.write( - "Registering %s handler for %s ...\n" % (handler.name, locus.filename) - ) - else: - raise TypeError("locus should be gdb.Progspace or None") - - # Some sanity checks on HANDLER. Calling getattr will raise an - # exception if the attribute doesn't exist, which is what we want. - # These checks are not exhaustive; we don't check the attributes - # have the correct types, or the method has the correct signature, - # but this should catch some basic mistakes. - getattr(handler, "name") - getattr(handler, "enabled") - call_method = getattr(handler, "__call__") - if not callable(call_method): - raise AttributeError( - "'%s' object's '__call__' attribute is not callable" - % type(handler).__name__ - ) - - i = 0 - for needle in locus.missing_debug_handlers: - if needle.name == handler.name: - if replace: - del locus.missing_debug_handlers[i] - else: - raise RuntimeError("Handler %s already exists." % handler.name) - i += 1 - locus.missing_debug_handlers.insert(0, handler) + """See gdb.missing_files.register_handler.""" + gdb.missing_files.register_handler("debug", locus, handler, replace) diff --git a/gdb/python/lib/gdb/missing_files.py b/gdb/python/lib/gdb/missing_files.py new file mode 100644 index 0000000..9f24db7 --- /dev/null +++ b/gdb/python/lib/gdb/missing_files.py @@ -0,0 +1,204 @@ +# 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 +# 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/>. + +""" +MissingFileHandler base class, and support functions used by the +missing_debug.py and missing_objfile.py modules. +""" + +import sys + +import gdb + +if sys.version_info >= (3, 7): + # Functions str.isascii() and str.isalnum are available starting Python + # 3.7. + def isascii(ch): + return ch.isascii() + + def isalnum(ch): + return ch.isalnum() + +else: + # Older version of Python doesn't have str.isascii() and + # str.isalnum() so provide our own. + # + # We could import isalnum() and isascii() from the curses library, + # but that adds an extra dependency. Given these functions are + # both small and trivial lets implement them here. + # + # These definitions are based on those in the curses library, but + # simplified as we know C will always be a single character 'str'. + + def isdigit(c): + return 48 <= ord(c) <= 57 + + def islower(c): + return 97 <= ord(c) <= 122 + + def isupper(c): + return 65 <= ord(c) <= 90 + + def isalpha(c): + return isupper(c) or islower(c) + + def isalnum(c): + return isalpha(c) or isdigit(c) + + def isascii(c): + return 0 <= ord(c) <= 127 + + +def _validate_name(name): + """Validate a missing file handler name string. + + If name is valid as a missing file handler name, then this + function does nothing. If name is not valid then an exception is + raised. + + Arguments: + name: A string, the name of a missing file handler. + + Returns: + Nothing. + + Raises: + ValueError: If name is invalid as a missing file handler + name. + """ + + for ch in name: + if not isascii(ch) or not (isalnum(ch) or ch in "_-"): + raise ValueError("invalid character '%s' in handler name: %s" % (ch, name)) + + +class MissingFileHandler(object): + """Base class for missing file handlers written in Python. + + A missing file handler has a single method __call__ along with the + read/write attribute enabled, and a read-only attribute name. The + attributes are provided by this class while the __call__ method is + provided by a sub-class. Each sub-classes __call__ method will + have a different signature. + + Attributes: + name: Read-only attribute, the name of this handler. + enabled: When true this handler is enabled. + """ + + def __init__(self, name): + """Constructor. + + Args: + name: An identifying name for this handler. + + Raises: + TypeError: name is not a string. + ValueError: name contains invalid characters. + """ + + if not isinstance(name, str): + raise TypeError("incorrect type for name: %s" % type(name)) + + _validate_name(name) + + self._name = name + self._enabled = True + + @property + def name(self): + return self._name + + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, value): + if not isinstance(value, bool): + raise TypeError("incorrect type for enabled attribute: %s" % type(value)) + self._enabled = value + + +def register_handler(handler_type, locus, handler, replace=False): + """Register handler in given locus. + + The handler is prepended to the locus's missing file handlers + list. The name of handler should be unique (or replace must be + True), and the name must pass the _validate_name check. + + Arguments: + handler_type: A string, either 'debug' or 'objfile' indicating the + type of handler to be registered. + locus: Either a progspace, or None (in which case the unwinder + is registered globally). + handler: An object used as a missing file handler. Usually a + sub-class of MissingFileHandler. + replace: If True, replaces existing handler with the same name + within locus. Otherwise, raises RuntimeException if + unwinder with the same name already exists. + + Returns: + Nothing. + + Raises: + RuntimeError: The name of handler is not unique. + TypeError: Bad locus type. + AttributeError: Required attributes of handler are missing. + ValueError: If the name of the handler is invalid, or if + handler_type is neither 'debug' or 'objfile'. + """ + + if handler_type != "debug" and handler_type != "objfile": + raise ValueError("handler_type must be 'debug' or 'objfile'") + + if locus is None: + if gdb.parameter("verbose"): + gdb.write("Registering global %s handler ...\n" % handler.name) + locus = gdb + elif isinstance(locus, gdb.Progspace): + if gdb.parameter("verbose"): + gdb.write( + "Registering %s handler for %s ...\n" % (handler.name, locus.filename) + ) + else: + raise TypeError("locus should be gdb.Progspace or None") + + # Some sanity checks on HANDLER. Calling getattr will raise an + # exception if the attribute doesn't exist, which is what we want. + # These checks are not exhaustive; we don't check the attributes + # have the correct types, or the method has the correct signature, + # but this should catch some basic mistakes. + name = getattr(handler, "name") + _validate_name(name) + + getattr(handler, "enabled") + + call_method = getattr(handler, "__call__") + if not callable(call_method): + raise AttributeError( + "'%s' object's '__call__' attribute is not callable" + % type(handler).__name__ + ) + + i = 0 + for needle in locus.missing_file_handlers: + if needle[0] == handler_type and needle[1].name == handler.name: + if replace: + del locus.missing_file_handlers[i] + else: + raise RuntimeError("Handler %s already exists." % handler.name) + i += 1 + locus.missing_file_handlers.insert(0, (handler_type, handler)) diff --git a/gdb/python/lib/gdb/missing_objfile.py b/gdb/python/lib/gdb/missing_objfile.py new file mode 100644 index 0000000..3d06bdd --- /dev/null +++ b/gdb/python/lib/gdb/missing_objfile.py @@ -0,0 +1,67 @@ +# 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 +# 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/>. + +""" +MissingObjfileHandler base class, and register_handler function. +""" + +import gdb +from gdb.missing_files import MissingFileHandler + + +class MissingObjfileHandler(MissingFileHandler): + """Base class for missing objfile handlers written in Python. + + A missing objfile handler has a single method __call__ along with + the read/write attribute enabled, and a read-only attribute name. + + Attributes: + name: Read-only attribute, the name of this handler. + enabled: When true this handler is enabled. + """ + + def __call__(self, buildid, filename): + """Handle a missing objfile when GDB can knows the build-id. + + Arguments: + + buildid: A string containing the build-id for the objfile + GDB is searching for. + filename: A string containing the name of the file GDB is + searching for. This is provided only for the purpose + of creating diagnostic messages. If the file is found + it does not have to be placed here, and this file + might already exist but GDB has determined it is not + suitable for use, e.g. if the build-id doesn't match. + + Returns: + + True: GDB should try again to locate the missing objfile, + the handler may have installed the missing file. + False: GDB should move on without the objfile. The + handler has determined that this objfile is not + available. + A string: GDB should load the file at the given path; it + contains the requested objfile. + None: This handler can't help with this objfile. GDB + should try any other registered handlers. + + """ + raise NotImplementedError("MissingObjfileHandler.__call__()") + + +def register_handler(locus, handler, replace=False): + """See gdb.missing_files.register_handler.""" + gdb.missing_files.register_handler("objfile", locus, handler, replace) 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/printer/bound_registers.py b/gdb/python/lib/gdb/printer/bound_registers.py deleted file mode 100644 index d00b455..0000000 --- a/gdb/python/lib/gdb/printer/bound_registers.py +++ /dev/null @@ -1,39 +0,0 @@ -# Pretty-printers for bounds registers. -# Copyright (C) 2013-2024 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/>. - -import gdb -import gdb.printing - - -class MpxBound128Printer(gdb.ValuePrinter): - """Adds size field to a mpx __gdb_builtin_type_bound128 type.""" - - def __init__(self, val): - self.__val = val - - def to_string(self): - upper = self.__val["ubound"] - lower = self.__val["lbound"] - size = upper - lower - if size > -1: - size = size + 1 - result = "{lbound = %s, ubound = %s} : size %s" % (lower, upper, size) - return result - - -gdb.printing.add_builtin_pretty_printer( - "mpx_bound128", "^builtin_type_bound128", MpxBound128Printer -) diff --git a/gdb/python/lib/gdb/printing.py b/gdb/python/lib/gdb/printing.py index 55ba435..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 @@ -281,6 +281,44 @@ class NoOpScalarPrinter(gdb.ValuePrinter): return self.__value.format_string(raw=True) +class NoOpStringPrinter(gdb.ValuePrinter): + """A no-op pretty printer that wraps a string value.""" + + def __init__(self, ty, value): + self.__ty = ty + self.__value = value + + def to_string(self): + # We need some special cases here. + # + # * If the gdb.Value was created from a Python string, it will + # be a non-lazy array -- but will have address 0 and so the + # contents will be lost on conversion to lazy string. + # (Weirdly, the .address attribute will not be 0 though.) + # Since conversion to lazy string is to avoid fetching too + # much data, and since the array is already non-lazy, just + # return it. + # + # * To avoid weird printing for a C "string" that is just a + # NULL pointer, special case this as well. + # + # * Lazy strings only understand arrays and pointers; other + # string-like objects (like a Rust &str) should simply be + # returned. + code = self.__ty.code + if code == gdb.TYPE_CODE_ARRAY and not self.__value.is_lazy: + return self.__value + elif code == gdb.TYPE_CODE_PTR and self.__value == 0: + return self.__value + elif code != gdb.TYPE_CODE_PTR and code != gdb.TYPE_CODE_ARRAY: + return self.__value + else: + return self.__value.lazy_string() + + def display_hint(self): + return "string" + + class NoOpPointerReferencePrinter(gdb.ValuePrinter): """A no-op pretty printer that wraps a pointer or reference.""" @@ -368,7 +406,7 @@ def make_visualizer(value): else: ty = value.type.strip_typedefs() if ty.is_string_like: - result = NoOpScalarPrinter(value) + result = NoOpStringPrinter(ty, value) elif ty.code == gdb.TYPE_CODE_ARRAY: result = NoOpArrayPrinter(ty, value) elif ty.is_array_like: 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 new file mode 100644 index 0000000..fcc72de --- /dev/null +++ b/gdb/python/lib/gdb/ptwrite.py @@ -0,0 +1,77 @@ +# Ptwrite utilities. +# 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 +# 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/>. + +"""Utilities for working with ptwrite filters.""" + +import gdb + +# _ptwrite_filter contains the per thread copies of the filter function. +# The keys are tuples of inferior id and thread id. +# The filter functions are created for each thread by calling the +# _ptwrite_filter_factory. +_ptwrite_filter = {} +_ptwrite_filter_factory = None + + +def _ptwrite_exit_handler(event): + """Exit handler to prune _ptwrite_filter on thread exit.""" + _ptwrite_filter.pop(event.inferior_thread.ptid, None) + + +gdb.events.thread_exited.connect(_ptwrite_exit_handler) + + +def _clear_traces(): + """Helper function to clear the trace of all threads.""" + current_thread = gdb.selected_thread() + + for inferior in gdb.inferiors(): + for thread in inferior.threads(): + thread.switch() + recording = gdb.current_recording() + if recording is not None: + recording.clear() + + current_thread.switch() + + +def register_filter_factory(filter_factory_): + """Register the ptwrite filter factory.""" + if filter_factory_ is not None and not callable(filter_factory_): + raise TypeError("The filter factory must be callable or 'None'.") + + # Clear the traces of all threads of all inferiors to force + # re-decoding with the new filter. + _clear_traces() + + _ptwrite_filter.clear() + global _ptwrite_filter_factory + _ptwrite_filter_factory = filter_factory_ + + +def get_filter(): + """Returns the filter of the current thread.""" + thread = gdb.selected_thread() + key = thread.ptid + + # Create a new filter for new threads. + if key not in _ptwrite_filter: + if _ptwrite_filter_factory is not None: + _ptwrite_filter[key] = _ptwrite_filter_factory(thread) + else: + return None + + return _ptwrite_filter[key] 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 c98402d..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 @@ -266,9 +266,14 @@ def register_xmethod_matcher(locus, matcher, replace=False): del locus.xmethods[index] else: raise RuntimeError( - "Xmethod matcher already registered with " - "%s: %s" % (locus_name, matcher.name) + "Xmethod matcher already registered with {}: {}".format( + locus_name, matcher.name + ) ) if gdb.parameter("verbose"): - gdb.write("Registering xmethod matcher '%s' with %s' ...\n") + gdb.write( + "Registering xmethod matcher '{}' with '{}' ...\n".format( + locus_name, matcher.name + ) + ) locus.xmethods.insert(0, matcher) diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def index 4df5541..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. @@ -45,3 +45,4 @@ GDB_PY_DEFINE_EVENT(connection_removed) GDB_PY_DEFINE_EVENT(executable_changed) GDB_PY_DEFINE_EVENT(new_progspace) GDB_PY_DEFINE_EVENT(free_progspace) +GDB_PY_DEFINE_EVENT(tui_enabled) diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c index c6f5662..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. @@ -199,8 +199,7 @@ archpy_disassemble (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } gdbpy_ref<> pc_obj = gdb_py_object_from_ulongest (pc); @@ -270,15 +269,16 @@ archpy_integer_type (PyObject *self, PyObject *args, PyObject *kw) { static const char *keywords[] = { "size", "signed", NULL }; int size; - PyObject *is_signed_obj = nullptr; + PyObject *is_signed_obj = Py_True; - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "i|O", keywords, - &size, &is_signed_obj)) + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "i|O!", keywords, + &size, + &PyBool_Type, &is_signed_obj)) return nullptr; /* Assume signed by default. */ - bool is_signed = (is_signed_obj == nullptr - || PyObject_IsTrue (is_signed_obj)); + gdb_assert (PyBool_Check (is_signed_obj)); + bool is_signed = is_signed_obj == Py_True; struct gdbarch *gdbarch; ARCHPY_REQUIRE_VALID (self, gdbarch); @@ -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 * @@ -362,11 +372,7 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_arch (void) { arch_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&arch_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Architecture", - (PyObject *) &arch_object_type); + return gdbpy_type_ready (&arch_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_arch); @@ -387,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 9548973..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. @@ -61,8 +61,8 @@ gdbpy_initialize_auto_load (void) { add_setshow_boolean_cmd ("python-scripts", class_support, &auto_load_python_scripts, _("\ -Set the debugger's behaviour regarding auto-loaded Python scripts."), _("\ -Show the debugger's behaviour regarding auto-loaded Python scripts."), _("\ +Set the debugger's behavior regarding auto-loaded Python scripts."), _("\ +Show the debugger's behavior regarding auto-loaded Python scripts."), _("\ If enabled, auto-loaded Python scripts are loaded when the debugger reads\n\ an executable or shared library.\n\ This options has security implications for untrusted inferiors."), @@ -73,10 +73,10 @@ This options has security implications for untrusted inferiors."), set_show_commands auto_load_scripts_cmds = add_setshow_boolean_cmd ("auto-load-scripts", class_support, &auto_load_python_scripts, _("\ -Set the debugger's behaviour regarding auto-loaded Python scripts, " +Set the debugger's behavior regarding auto-loaded Python scripts, " "deprecated."), _("\ -Show the debugger's behaviour regarding auto-loaded Python scripts, " +Show the debugger's behavior regarding auto-loaded Python scripts, " "deprecated."), NULL, NULL, show_auto_load_python_scripts, &setlist, &showlist); diff --git a/gdb/python/py-block.c b/gdb/python/py-block.c index 62e93d5..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 * @@ -493,19 +524,14 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_blocks (void) { block_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&block_object_type) < 0) + if (gdbpy_type_ready (&block_object_type) < 0) return -1; block_syms_iterator_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&block_syms_iterator_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "Block", - (PyObject *) &block_object_type) < 0) + if (gdbpy_type_ready (&block_syms_iterator_object_type) < 0) return -1; - return gdb_pymodule_addobject (gdb_module, "BlockIterator", - (PyObject *) &block_syms_iterator_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_blocks); @@ -534,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 e7dd470..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" @@ -207,7 +208,7 @@ bppy_set_enabled (PyObject *self, PyObject *newvalue, void *closure) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } return 0; @@ -394,7 +395,7 @@ bppy_set_task (PyObject *self, PyObject *newvalue, void *closure) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } if (! valid_id) @@ -443,7 +444,7 @@ bppy_delete_breakpoint (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -484,7 +485,7 @@ bppy_set_ignore_count (PyObject *self, PyObject *newvalue, void *closure) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } return 0; @@ -611,9 +612,9 @@ bppy_set_condition (PyObject *self, PyObject *newvalue, void *closure) { set_breakpoint_condition (self_bp->bp, exp, 0, false); } - catch (gdb_exception &ex) + catch (const gdb_exception &ex) { - GDB_PY_SET_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (-1, ex); } return 0; @@ -640,8 +641,7 @@ bppy_get_commands (PyObject *self, void *closure) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } return host_string_to_python_string (stb.c_str ()).release (); @@ -677,9 +677,9 @@ bppy_set_commands (PyObject *self, PyObject *newvalue, void *closure) counted_command_line lines = read_command_lines_1 (reader, 1, nullptr); breakpoint_set_commands (self_bp->bp, std::move (lines)); } - catch (gdb_exception &ex) + catch (const gdb_exception &ex) { - GDB_PY_SET_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (-1, ex); } return 0; @@ -928,14 +928,14 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) char *label = NULL; char *source = NULL; char *function = NULL; - PyObject * qualified = NULL; + PyObject *qualified = Py_False; - if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "|siiOOsssOO", keywords, + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "|siiOOsssOO!", keywords, &spec, &type, &access_type, &internal, &temporary, &source, &function, &label, &lineobj, - &qualified)) + &PyBool_Type, &qualified)) return -1; @@ -948,7 +948,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) else { PyErr_SetString (PyExc_RuntimeError, - _("Line keyword should be an integer or a string. ")); + _("Line keyword should be an integer or a string.")); return -1; } } @@ -983,10 +983,11 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) case bp_hardware_breakpoint: { location_spec_up locspec; + gdb_assert (PyBool_Check (qualified)); symbol_name_match_type func_name_match_type - = (qualified != NULL && PyObject_IsTrue (qualified) - ? symbol_name_match_type::FULL - : symbol_name_match_type::WILD); + = (qualified == Py_True + ? symbol_name_match_type::FULL + : symbol_name_match_type::WILD); if (spec != NULL) { @@ -1055,8 +1056,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) catch (const gdb_exception &except) { bppy_pending_object = NULL; - gdbpy_convert_exception (except); - return -1; + return gdbpy_handle_gdb_exception (-1, except); } BPPY_SET_REQUIRE_VALID ((gdbpy_breakpoint_object *) self); @@ -1116,7 +1116,7 @@ gdbpy_breakpoint_init_breakpoint_type () if (breakpoint_object_type.tp_new == nullptr) { breakpoint_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&breakpoint_object_type) < 0) + if (gdbpy_type_ready (&breakpoint_object_type) < 0) { /* Reset tp_new back to nullptr so future calls to this function will try calling PyType_Ready again. */ @@ -1361,10 +1361,6 @@ gdbpy_initialize_breakpoints (void) if (!gdbpy_breakpoint_init_breakpoint_type ()) return -1; - if (gdb_pymodule_addobject (gdb_module, "Breakpoint", - (PyObject *) &breakpoint_object_type) < 0) - return -1; - gdb::observers::breakpoint_created.attach (gdbpy_breakpoint_created, "py-breakpoint"); gdb::observers::breakpoint_deleted.attach (gdbpy_breakpoint_deleted, @@ -1396,14 +1392,7 @@ gdbpy_initialize_breakpoints (void) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_breakpoint_locations () { - if (PyType_Ready (&breakpoint_location_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "BreakpointLocation", - (PyObject *) &breakpoint_location_object_type) - < 0) - return -1; - return 0; + return gdbpy_type_ready (&breakpoint_location_object_type); } @@ -1548,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, @@ -1597,7 +1584,7 @@ bplocpy_set_enabled (PyObject *py_self, PyObject *newvalue, void *closure) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } return 0; } @@ -1641,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. */ @@ -1655,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); @@ -1702,7 +1707,7 @@ bplocpy_get_thread_groups (PyObject *py_self, void *closure) gdbpy_ref<> num = gdb_py_object_from_ulongest (inf->num); if (num == nullptr) return nullptr; - if (PyList_Append (list.get (), num.release ()) != 0) + if (PyList_Append (list.get (), num.get ()) != 0) return nullptr; } } diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index f83b45d..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. @@ -39,7 +39,7 @@ struct cmdpy_completer static const struct cmdpy_completer completers[] = { { "COMPLETE_NONE", noop_completer }, - { "COMPLETE_FILENAME", filename_completer }, + { "COMPLETE_FILENAME", filename_maybe_quoted_completer }, { "COMPLETE_LOCATION", location_completer }, { "COMPLETE_COMMAND", command_completer }, { "COMPLETE_SYMBOL", symbol_completer }, @@ -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; @@ -541,8 +585,7 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return -1; + return gdbpy_handle_gdb_exception (-1, except); } return 0; @@ -558,7 +601,7 @@ gdbpy_initialize_commands (void) int i; cmdpy_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&cmdpy_object_type) < 0) + if (gdbpy_type_ready (&cmdpy_object_type) < 0) return -1; /* Note: alias and user are special. */ @@ -588,10 +631,6 @@ gdbpy_initialize_commands (void) return -1; } - if (gdb_pymodule_addobject (gdb_module, "Command", - (PyObject *) &cmdpy_object_type) < 0) - return -1; - invoke_cst = PyUnicode_FromString ("invoke"); if (invoke_cst == NULL) return -1; diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c new file mode 100644 index 0000000..3bbd22d --- /dev/null +++ b/gdb/python/py-color.c @@ -0,0 +1,345 @@ +/* Python interface to ui_file_style::color objects. + + Copyright (C) 2008-2025 Free Software Foundation, Inc. + + This file is part of GDB. + + 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/>. */ + + +#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 { + const char *name; + color_space value; +} colorspace_constants[] = +{ + { "COLORSPACE_MONOCHROME", color_space::MONOCHROME }, + { "COLORSPACE_ANSI_8COLOR", color_space::ANSI_8COLOR }, + { "COLORSPACE_AIXTERM_16COLOR", color_space::AIXTERM_16COLOR }, + { "COLORSPACE_XTERM_256COLOR", color_space::XTERM_256COLOR }, + { "COLORSPACE_RGB_24BIT", color_space::RGB_24BIT }, +}; + +/* A color. */ +struct colorpy_object +{ + PyObject_HEAD + + /* Underlying value. */ + ui_file_style::color color; +}; + +extern PyTypeObject colorpy_object_type; + +/* See py-color.h. */ +gdbpy_ref<> +create_color_object (const ui_file_style::color &color) +{ + gdbpy_ref<colorpy_object> color_obj (PyObject_New (colorpy_object, + &colorpy_object_type)); + + if (color_obj == nullptr) + return nullptr; + + color_obj->color = color; + return gdbpy_ref<> ((PyObject *) color_obj.release ()); +} + +/* See py-color.h. */ +bool +gdbpy_is_color (PyObject *obj) +{ + gdb_assert (obj != nullptr); + return PyObject_TypeCheck (obj, &colorpy_object_type) != 0; +} + +/* See py-color.h. */ +const ui_file_style::color & +gdbpy_get_color (PyObject *obj) +{ + gdb_assert (gdbpy_is_color (obj)); + colorpy_object *self = (colorpy_object *) obj; + return self->color; +} + +/* Get an attribute. */ +static PyObject * +get_attr (PyObject *obj, PyObject *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")) + { + int value = static_cast<int> (color.colorspace ()); + return gdb_py_object_from_longest (value).release (); + } + + if (!PyUnicode_CompareWithASCIIString (attr_name, "is_none")) + return PyBool_FromLong (color.is_none ()); + + if (!PyUnicode_CompareWithASCIIString (attr_name, "is_indexed")) + return PyBool_FromLong (color.is_indexed ()); + + if (!PyUnicode_CompareWithASCIIString (attr_name, "is_direct")) + return PyBool_FromLong (color.is_direct ()); + + if (color.is_indexed () + && !PyUnicode_CompareWithASCIIString (attr_name, "index")) + return gdb_py_object_from_longest (color.get_value ()).release (); + + if (color.is_direct () + && !PyUnicode_CompareWithASCIIString (attr_name, "components")) + { + uint8_t rgb[3]; + color.get_rgb (rgb); + + gdbpy_ref<> rgb_objects[3]; + for (int i = 0; i < 3; ++i) + { + rgb_objects[i] = gdb_py_object_from_ulongest (rgb[i]); + if (rgb_objects[i] == nullptr) + return nullptr; + } + + PyObject *comp = PyTuple_New (3); + if (comp == nullptr) + return nullptr; + + for (int i = 0; i < 3; ++i) + PyTuple_SET_ITEM (comp, i, rgb_objects[i].release ()); + + return comp; + } + + return PyObject_GenericGetAttr (obj, attr_name); +} + +/* Implementation of Color.escape_sequence (self, is_fg) -> str. */ + +static PyObject * +colorpy_escape_sequence (PyObject *self, PyObject *args, PyObject *kwargs) +{ + 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; + + /* 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 ()) + { + bool is_fg = is_fg_obj == Py_True; + s = gdbpy_get_color (self).to_ansi (is_fg); + } + + return host_string_to_python_string (s.c_str ()).release (); +} + +/* Object initializer; fills color with value. + + Use: __init__(VALUE = None, COLORSPACE = None) + + VALUE is a string, integer, RGB-tuple or None. + + COLORSPACE is the color space index. + + Returns -1 on error, with a python exception set. */ + +static int +colorpy_init (PyObject *self, PyObject *args, PyObject *kwds) +{ + colorpy_object *obj = (colorpy_object *) self; + PyObject *value_obj = nullptr; + PyObject *colorspace_obj = nullptr; + color_space colorspace = color_space::MONOCHROME; + + 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 != nullptr) + { + if (PyLong_Check (colorspace_obj)) + { + long colorspace_id = -1; + 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); + } + else if (colorspace_obj == Py_None) + colorspace_obj = nullptr; + else + error (_("colorspace must be None or integer")); + } + + if (value_obj == nullptr || value_obj == Py_None) + obj->color = ui_file_style::color (colorspace, -1); + else if (PyLong_Check (value_obj)) + { + long value = -1; + 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 != nullptr) + obj->color = ui_file_style::color (colorspace, value); + else + obj->color = ui_file_style::color (value); + } + else if (PyTuple_Check (value_obj)) + { + if (colorspace_obj == nullptr || colorspace != color_space::RGB_24BIT) + error (_("colorspace must be gdb.COLORSPACE_RGB_24BIT with " + "value of tuple type.")); + Py_ssize_t tuple_size = PyTuple_Size (value_obj); + if (tuple_size < 0) + return -1; + if (tuple_size != 3) + error (_("Tuple value with RGB must be of size 3.")); + uint8_t rgb[3]; + for (int i = 0; i < 3; ++i) + { + PyObject *item = PyTuple_GetItem (value_obj, i); + if (!PyLong_Check (item)) + error (_("Item %d of an RGB tuple must be integer."), i); + long item_value = -1; + if (!gdb_py_int_as_long (item, &item_value)) + return -1; + if (item_value < 0 || item_value > UINT8_MAX) + error (_("RGB item %ld is out of byte range."), item_value); + rgb[i] = static_cast<uint8_t> (item_value); + } + + obj->color = ui_file_style::color (rgb[0], rgb[1], rgb[2]); + } + else if (PyUnicode_Check (value_obj)) + { + gdb::unique_xmalloc_ptr<char> + str (python_string_to_host_string (value_obj)); + if (str == nullptr) + return -1; + obj->color = parse_var_color (str.get()); + + if (colorspace_obj != nullptr + && colorspace != obj->color.colorspace ()) + error (_("colorspace doesn't match to the value.")); + } + else + error (_("value must be one of None, integer, tuple or str.")); + } + catch (const gdb_exception &except) + { + return gdbpy_handle_gdb_exception (-1, except); + } + + return 0; +} + +static PyObject * +colorpy_str (PyObject *self) +{ + colorpy_object *obj = reinterpret_cast<colorpy_object *> (self); + + return PyUnicode_FromString (obj->color.to_string ().c_str ()); +} + +/* Initialize the 'color' module. */ +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; + + colorpy_object_type.tp_new = PyType_GenericNew; + return gdbpy_type_ready (&colorpy_object_type, gdb_module); +} + +/* Color methods. */ + +static PyMethodDef color_methods[] = +{ + { "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."}, + {nullptr} +}; + +PyTypeObject colorpy_object_type = +{ + PyVarObject_HEAD_INIT (nullptr, 0) + "gdb.Color", /*tp_name*/ + sizeof (colorpy_object), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + colorpy_str, /*tp_str*/ + get_attr, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "GDB color object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + color_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + colorpy_init, /* tp_init */ + 0, /* tp_alloc */ +}; + +GDBPY_INITIALIZE_FILE (gdbpy_initialize_color); diff --git a/gdb/python/py-color.h b/gdb/python/py-color.h new file mode 100644 index 0000000..d94baa2 --- /dev/null +++ b/gdb/python/py-color.h @@ -0,0 +1,35 @@ +/* Python interface to ui_file_style::color objects. + + Copyright (C) 2009-2025 Free Software Foundation, Inc. + + This file is part of GDB. + + 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/>. */ + +#ifndef GDB_PYTHON_PY_COLOR_H +#define GDB_PYTHON_PY_COLOR_H + +#include "python-internal.h" +#include "ui-style.h" + +/* Create a new gdb.Color object from COLOR. */ +extern gdbpy_ref<> create_color_object (const ui_file_style::color &color); + +/* Check if OBJ is instance of a gdb.Color type. */ +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 /* GDB_PYTHON_PY_COLOR_H */ diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c index dcca76b..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. @@ -287,18 +286,10 @@ connpy_get_connection_details (PyObject *self, void *closure) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_connection (void) { - if (PyType_Ready (&connection_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "TargetConnection", - (PyObject *) &connection_object_type) < 0) + if (gdbpy_type_ready (&connection_object_type) < 0) return -1; - if (PyType_Ready (&remote_connection_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "RemoteTargetConnection", - (PyObject *) &remote_connection_object_type) < 0) + if (gdbpy_type_ready (&remote_connection_object_type) < 0) return -1; return 0; @@ -350,7 +341,7 @@ struct py_send_packet_callbacks : public send_remote_packet_callbacks It is important that the result is inspected immediately after sending a packet to the remote, and any error fetched, calling any other Python functions that might clear the error state, or rely on an error - not being set will cause undefined behaviour. */ + not being set will cause undefined behavior. */ gdbpy_ref<> result () const { @@ -431,16 +422,13 @@ connpy_send_packet (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return nullptr; + return gdbpy_handle_gdb_exception (nullptr, except); } } /* 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 87fea26..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) { @@ -308,7 +303,7 @@ disasm_info_dealloc (PyObject *self) NEXT is nullptr. */ Py_XDECREF ((PyObject *) obj->next); - /* Now core deallocation behaviour. */ + /* Now core deallocation behavior. */ Py_TYPE (self)->tp_free (self); } @@ -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 @@ -595,7 +589,7 @@ disasmpy_builtin_disassemble (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (!str.empty ()) PyErr_SetString (gdbpy_gdberror_exc, str.c_str ()); @@ -650,7 +644,7 @@ disasmpy_set_enabled (PyObject *self, PyObject *args, PyObject *kw) return nullptr; } - python_print_insn_enabled = PyObject_IsTrue (newstate); + python_print_insn_enabled = newstate == Py_True; Py_RETURN_NONE; } @@ -740,7 +734,7 @@ disasmpy_info_progspace (PyObject *self, void *closure) part in the gdbpy_disassembler is a text part in the same STYLE, then the new string is appended to the previous part. - The merging behaviour make the Python API a little more user friendly, + The merging behavior make the Python API a little more user friendly, some disassemblers produce their output character at a time, there's no particular reason for this, it's just how they are implemented. By merging parts with the same style we make it easier for the user to @@ -933,7 +927,7 @@ disasmpy_result_str (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_Decode (str.c_str (), str.size (), @@ -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 @@ -1306,7 +1298,7 @@ gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR memaddr, } else { - gdbpy_print_stack (); + gdbpy_print_stack_or_quit (); return std::optional<int> (-1); } @@ -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); } @@ -1512,7 +1505,7 @@ disasmpy_addr_part_str (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_Decode (str.c_str (), str.size (), @@ -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\ @@ -1665,45 +1657,23 @@ gdbpy_initialize_disasm () } disasm_info_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&disasm_info_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_disassembler_module, "DisassembleInfo", - (PyObject *) &disasm_info_object_type) < 0) + if (gdbpy_type_ready (&disasm_info_object_type, gdb_disassembler_module) < 0) return -1; disasm_result_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&disasm_result_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_disassembler_module, "DisassemblerResult", - (PyObject *) &disasm_result_object_type) < 0) + if (gdbpy_type_ready (&disasm_result_object_type, gdb_disassembler_module) < 0) return -1; disasm_part_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&disasm_part_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_disassembler_module, "DisassemblerPart", - (PyObject *) &disasm_part_object_type) < 0) + if (gdbpy_type_ready (&disasm_part_object_type, gdb_disassembler_module) < 0) return -1; disasm_addr_part_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&disasm_addr_part_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_disassembler_module, - "DisassemblerAddressPart", - (PyObject *) &disasm_addr_part_object_type) < 0) + if (gdbpy_type_ready (&disasm_addr_part_object_type, gdb_disassembler_module) < 0) return -1; disasm_text_part_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&disasm_text_part_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_disassembler_module, - "DisassemblerTextPart", - (PyObject *) &disasm_text_part_object_type) < 0) + if (gdbpy_type_ready (&disasm_text_part_object_type, gdb_disassembler_module) < 0) return -1; return 0; diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def index f43a51b..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. @@ -140,3 +140,8 @@ GDB_PY_DEFINE_EVENT_TYPE (free_progspace, "FreeProgspaceEvent", "GDB free Progspace event object", event_object_type); + +GDB_PY_DEFINE_EVENT_TYPE (tui_enabled, + "TuiEnabledEvent", + "GDB TUI enabled event object", + event_object_type); diff --git a/gdb/python/py-event.c b/gdb/python/py-event.c index 47a2997..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. @@ -56,25 +56,9 @@ evpy_add_attribute (PyObject *event, const char *name, PyObject *attr) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_event (void) { - return gdbpy_initialize_event_generic (&event_object_type, - "Event"); + return gdbpy_type_ready (&event_object_type); } -/* Initialize the given event type. If BASE is not NULL it will - be set as the types base. - Returns 0 if initialization was successful -1 otherwise. */ - -int -gdbpy_initialize_event_generic (PyTypeObject *type, - const char *name) -{ - if (PyType_Ready (type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, name, (PyObject *) type); -} - - /* Notify the list of listens that the given EVENT has occurred. returns 0 if emit is successful -1 otherwise. */ diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h index 388c513..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. @@ -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_EVENT_H -#define PYTHON_PY_EVENT_H +#ifndef GDB_PYTHON_PY_EVENT_H +#define GDB_PYTHON_PY_EVENT_H #include "py-events.h" #include "command.h" @@ -84,7 +84,5 @@ extern void evpy_dealloc (PyObject *self); extern int evpy_add_attribute (PyObject *event, const char *name, PyObject *attr) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; -int gdbpy_initialize_event_generic (PyTypeObject *type, const char *name) - CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; -#endif /* PYTHON_PY_EVENT_H */ +#endif /* GDB_PYTHON_PY_EVENT_H */ diff --git a/gdb/python/py-events.h b/gdb/python/py-events.h index e628e0f..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. @@ -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_EVENTS_H -#define PYTHON_PY_EVENTS_H +#ifndef GDB_PYTHON_PY_EVENTS_H +#define GDB_PYTHON_PY_EVENTS_H #include "command.h" #include "python-internal.h" @@ -54,4 +54,4 @@ extern events_object gdb_py_events; extern eventregistry_object *create_eventregistry_object (void); extern bool evregpy_no_listeners_p (eventregistry_object *registry); -#endif /* PYTHON_PY_EVENTS_H */ +#endif /* GDB_PYTHON_PY_EVENTS_H */ diff --git a/gdb/python/py-evtregistry.c b/gdb/python/py-evtregistry.c index 1f486e2..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. @@ -104,11 +104,7 @@ evregpy_dealloc (PyObject *self) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_eventregistry (void) { - if (PyType_Ready (&eventregistry_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "EventRegistry", - (PyObject *) &eventregistry_object_type); + return gdbpy_type_ready (&eventregistry_object_type); } /* Return the number of listeners currently connected to this 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 78030be..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. @@ -217,8 +217,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return -1; + return gdbpy_handle_gdb_exception (-1, except); } if (PyErr_Occurred ()) @@ -318,7 +317,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } self_bpfinish->py_bp.bp->frame_id = frame_id; @@ -440,11 +439,7 @@ gdbpy_initialize_finishbreakpoints (void) if (!gdbpy_breakpoint_init_breakpoint_type ()) return -1; - if (PyType_Ready (&finish_breakpoint_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "FinishBreakpoint", - (PyObject *) &finish_breakpoint_object_type) < 0) + if (gdbpy_type_ready (&finish_breakpoint_object_type) < 0) return -1; gdb::observers::normal_stop.attach (bpfinishpy_handle_stop, diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c index bbb42af..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. @@ -115,7 +115,7 @@ frapy_is_valid (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (frame == NULL) @@ -143,7 +143,7 @@ frapy_name (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (name) @@ -177,7 +177,7 @@ frapy_type (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return gdb_py_object_from_longest (type).release (); @@ -198,7 +198,7 @@ frapy_arch (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return gdbarch_to_arch_object (obj->gdbarch); @@ -219,7 +219,7 @@ frapy_unwind_stop_reason (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } stop_reason = get_frame_unwind_stop_reason (frame); @@ -244,7 +244,7 @@ frapy_pc (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return gdb_py_object_from_ulongest (pc).release (); @@ -286,7 +286,7 @@ frapy_read_register (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -308,7 +308,7 @@ frapy_block (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } for (fn_block = block; @@ -347,7 +347,7 @@ frapy_function (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (sym) @@ -389,8 +389,7 @@ frame_info_to_frame_object (const frame_info_ptr &frame) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } return (PyObject *) frame_obj.release (); @@ -414,7 +413,7 @@ frapy_older (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (prev) @@ -446,7 +445,7 @@ frapy_newer (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (next) @@ -478,7 +477,7 @@ frapy_find_sal (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return sal_obj; @@ -538,8 +537,7 @@ frapy_read_var (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } if (!var) @@ -569,7 +567,7 @@ frapy_read_var (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -590,7 +588,7 @@ frapy_select (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -611,7 +609,7 @@ frapy_level (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -634,7 +632,7 @@ frapy_language (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -655,7 +653,7 @@ frapy_static_link (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (link == nullptr) @@ -678,7 +676,7 @@ gdbpy_newest_frame (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return frame_info_to_frame_object (frame); @@ -698,7 +696,7 @@ gdbpy_selected_frame (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return frame_info_to_frame_object (frame); @@ -763,7 +761,7 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_frames (void) { frame_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&frame_object_type) < 0) + if (gdbpy_type_ready (&frame_object_type) < 0) return -1; /* Note: These would probably be best exposed as class attributes of @@ -787,8 +785,7 @@ gdbpy_initialize_frames (void) #include "unwind_stop_reasons.def" #undef SET - return gdb_pymodule_addobject (gdb_module, "Frame", - (PyObject *) &frame_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_frames); diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c index 89695ff..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. @@ -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 "gdbsupport/unordered_set.h" #include "objfiles.h" #include "symtab.h" #include "language.h" @@ -27,7 +28,6 @@ #include "stack.h" #include "source.h" #include "annotate.h" -#include "hashtab.h" #include "demangle.h" #include "mi/mi-cmds.h" #include "python-internal.h" @@ -731,25 +731,37 @@ py_print_args (PyObject *filter, return EXT_LANG_BT_OK; } +using levels_printed_hash = gdb::unordered_set<frame_info *>; + /* Print a single frame to the designated output stream, detecting whether the output is MI or console, and formatting the output - according to the conventions of that protocol. FILTER is the - frame-filter associated with this frame. FLAGS is an integer - describing the various print options. The FLAGS variables is - described in "apply_frame_filter" function. ARGS_TYPE is an - enumerator describing the argument format. OUT is the output - stream to print, INDENT is the level of indention for this frame - (in the case of elided frames), and LEVELS_PRINTED is a hash-table - containing all the frames level that have already been printed. - If a frame level has been printed, do not print it again (in the - case of elided frames). Returns EXT_LANG_BT_ERROR on error, with any - GDB exceptions converted to a Python exception, or EXT_LANG_BT_OK - on success. It can also throw an exception RETURN_QUIT. */ + according to the conventions of that protocol. + + FILTER is the frame-filter associated with this frame. + + FLAGS is an integer describing the various print options. The FLAGS + variables is described in "apply_frame_filter" function. + + ARGS_TYPE is an enumerator describing the argument format. + + OUT is the output stream to print to. + + 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 + level has already been printed. If a level has been printed, do not print + it again (in the case of elided frames). + + Returns EXT_LANG_BT_ERROR on error, with any GDB exceptions converted to a + Python exception, or EXT_LANG_BT_OK on success. It can also throw an + exception RETURN_QUIT. */ static enum ext_lang_bt_status py_print_frame (PyObject *filter, frame_filter_flags flags, enum ext_lang_frame_args args_type, - struct ui_out *out, int indent, htab_t levels_printed) + struct ui_out *out, int indent, + levels_printed_hash &levels_printed) { int has_addr = 0; CORE_ADDR address = 0; @@ -762,7 +774,7 @@ py_print_frame (PyObject *filter, frame_filter_flags flags, default value for the backtrace command (see the call to print_frame_info in backtrace_command_1). Having the same default ensures that 'bt' and 'bt no-filters' - have the same behaviour when some filters exist but do not apply + have the same behavior when some filters exist but do not apply to a frame. */ enum print_what print_what = out->is_mi_like_p () ? LOC_AND_ADDRESS : LOCATION; @@ -822,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); @@ -859,23 +871,16 @@ py_print_frame (PyObject *filter, frame_filter_flags flags, && (location_print || (out->is_mi_like_p () && (print_frame_info || print_args)))) { - struct frame_info **slot; - int level; - - slot = (frame_info **) htab_find_slot (levels_printed, - frame.get(), INSERT); - - level = frame_relative_level (frame); - /* Check if this frame has already been printed (there are cases where elided synthetic dummy-frames have to 'borrow' the frame architecture from the eliding frame. If that is the case, do - not print 'level', but print spaces. */ - if (*slot == frame) + not print the level, but print spaces. */ + if (!levels_printed.insert (frame.get ()).second) out->field_skip ("level"); else { - *slot = frame.get (); + int level = frame_relative_level (frame); + annotate_frame_begin (print_level ? level : 0, gdbarch, address); out->text ("#"); @@ -923,12 +928,12 @@ py_print_frame (PyObject *filter, frame_filter_flags flags, else if (PyLong_Check (py_func.get ())) { CORE_ADDR addr; - struct bound_minimal_symbol msymbol; if (get_addr_from_python (py_func.get (), &addr) < 0) return EXT_LANG_BT_ERROR; - msymbol = lookup_minimal_symbol_by_pc (addr); + bound_minimal_symbol msymbol + = lookup_minimal_symbol_by_pc (addr); if (msymbol.minsym != NULL) function = msymbol.minsym->print_name (); } @@ -1007,7 +1012,7 @@ py_print_frame (PyObject *filter, frame_filter_flags flags, out->text (":"); annotate_frame_source_line (); - out->field_signed ("line", line); + out->field_signed ("line", line, line_number_style.style ()); } } if (out->is_mi_like_p ()) @@ -1197,10 +1202,7 @@ gdbpy_apply_frame_filter (const struct extension_language_defn *extlang, if (iterable == Py_None) return EXT_LANG_BT_NO_FILTERS; - htab_up levels_printed (htab_create (20, - htab_hash_pointer, - htab_eq_pointer, - NULL)); + levels_printed_hash levels_printed; while (true) { @@ -1232,7 +1234,7 @@ gdbpy_apply_frame_filter (const struct extension_language_defn *extlang, try { success = py_print_frame (item.get (), flags, args_type, out, 0, - levels_printed.get ()); + levels_printed); } catch (const gdb_exception_error &except) { diff --git a/gdb/python/py-function.c b/gdb/python/py-function.c index 2bbfb9d..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. @@ -120,6 +120,8 @@ fnpy_init (PyObject *self, PyObject *args, PyObject *kwds) docstring = python_string_to_host_string (ds_obj.get ()); if (docstring == NULL) return -1; + docstring + = gdbpy_fix_doc_string_indentation (std::move (docstring)); } } } @@ -137,11 +139,7 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_functions (void) { fnpy_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&fnpy_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Function", - (PyObject *) &fnpy_object_type); + return gdbpy_type_ready (&fnpy_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_functions); diff --git a/gdb/python/py-gdb-readline.c b/gdb/python/py-gdb-readline.c index 92287ee..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; @@ -59,8 +55,7 @@ gdbpy_readline_wrapper (FILE *sys_stdin, FILE *sys_stdout, /* This readline callback is called without the GIL held. */ gdbpy_gil gil; - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } /* Detect EOF (Ctrl-D). */ diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c index a1042ee..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 { @@ -412,7 +412,7 @@ infpy_threads (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } tuple = PyTuple_New (inf_obj->threads->size ()); @@ -578,7 +578,7 @@ infpy_read_memory (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } @@ -631,9 +631,9 @@ infpy_write_memory (PyObject *self, PyObject *args, PyObject *kw) write_memory_with_notification (addr, buffer, length); } - catch (gdb_exception &ex) + catch (const gdb_exception &ex) { - GDB_PY_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (nullptr, ex); } Py_RETURN_NONE; @@ -705,9 +705,9 @@ infpy_search_memory (PyObject *self, PyObject *args, PyObject *kw) buffer, pattern_size, &found_addr); } - catch (gdb_exception &ex) + catch (const gdb_exception &ex) { - GDB_PY_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (nullptr, ex); } if (found) @@ -783,7 +783,7 @@ infpy_thread_from_thread_handle (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -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 { @@ -1009,11 +1009,7 @@ gdbpy_selected_inferior (PyObject *self, PyObject *args) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_inferior (void) { - if (PyType_Ready (&inferior_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "Inferior", - (PyObject *) &inferior_object_type) < 0) + if (gdbpy_type_ready (&inferior_object_type) < 0) return -1; gdb::observers::new_thread.attach (add_thread_object, "py-inferior"); 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 a17f25e..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. @@ -104,7 +104,7 @@ thpy_get_details (PyObject *self, void *ignore) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (extra_info == nullptr) Py_RETURN_NONE; @@ -212,7 +212,7 @@ thpy_get_ptid_string (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } } @@ -245,7 +245,7 @@ thpy_switch (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -330,7 +330,7 @@ thpy_thread_handle (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (hv.size () == 0) @@ -412,11 +412,7 @@ gdbpy_selected_thread (PyObject *self, PyObject *args) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_thread (void) { - if (PyType_Ready (&thread_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "InferiorThread", - (PyObject *) &thread_object_type); + return gdbpy_type_ready (&thread_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_thread); diff --git a/gdb/python/py-instruction.c b/gdb/python/py-instruction.c index bc3945a..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. @@ -66,7 +66,7 @@ py_insn_get_insn_type () py_insn_type.tp_doc = "GDB instruction object"; py_insn_type.tp_getset = py_insn_getset; - if (PyType_Ready (&py_insn_type) < 0) + if (gdbpy_type_ready (&py_insn_type) < 0) { /* Reset the tp_new field so any subsequent calls to this function will retry to make the type ready. */ diff --git a/gdb/python/py-instruction.h b/gdb/python/py-instruction.h index 85604de..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. @@ -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_INSTRUCTION_H -#define PYTHON_PY_INSTRUCTION_H +#ifndef GDB_PYTHON_PY_INSTRUCTION_H +#define GDB_PYTHON_PY_INSTRUCTION_H #include "python-internal.h" @@ -32,4 +32,4 @@ extern PyTypeObject *py_insn_get_insn_type (); -#endif /* PYTHON_PY_INSTRUCTION_H */ +#endif /* GDB_PYTHON_PY_INSTRUCTION_H */ diff --git a/gdb/python/py-lazy-string.c b/gdb/python/py-lazy-string.c index 8779716..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 @@ -148,7 +148,7 @@ stpy_convert_to_value (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -182,14 +182,6 @@ gdbpy_create_lazy_string_object (CORE_ADDR address, long length, return NULL; } - if (address == 0 && length != 0) - { - PyErr_SetString (gdbpy_gdb_memory_error, - _("Cannot create a lazy string with address 0x0, " \ - "and a non-zero length.")); - return NULL; - } - if (!type) { PyErr_SetString (PyExc_RuntimeError, @@ -216,6 +208,23 @@ gdbpy_create_lazy_string_object (CORE_ADDR address, long length, } break; } + + case TYPE_CODE_PTR: + if (address == 0) + { + if (length > 0) + { + PyErr_SetString (gdbpy_gdb_memory_error, + _("Cannot create a lazy string with address 0x0, " \ + "and a non-zero length.")); + return nullptr; + } + length = 0; + } + break; + + default: + gdb_assert_not_reached ("invalid type in gdbpy_create_lazy_string_object"); } str_obj = PyObject_New (lazy_string_object, &lazy_string_object_type); @@ -236,11 +245,7 @@ gdbpy_create_lazy_string_object (CORE_ADDR address, long length, static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_lazy_string (void) { - if (PyType_Ready (&lazy_string_object_type) < 0) - return -1; - - Py_INCREF (&lazy_string_object_type); - return 0; + return gdbpy_type_ready (&lazy_string_object_type); } /* Determine whether the printer object pointed to by OBJ is a @@ -268,9 +273,7 @@ stpy_lazy_string_elt_type (lazy_string_object *lazy) case TYPE_CODE_ARRAY: return check_typedef (realtype->target_type ()); default: - /* This is done to preserve existing behaviour. PR 20769. - E.g., gdb.parse_and_eval("my_int_variable").lazy_string().type. */ - return realtype; + gdb_assert_not_reached ("invalid lazy string"); } } @@ -315,7 +318,7 @@ stpy_str (PyObject *self) } catch (const gdb_exception &exc) { - GDB_PY_HANDLE_EXCEPTION (exc); + return gdbpy_handle_gdb_exception (nullptr, exc); } return host_string_to_python_string (stream.c_str ()).release (); diff --git a/gdb/python/py-linetable.c b/gdb/python/py-linetable.c index e3e71f9..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. @@ -169,7 +169,7 @@ ltpy_get_pcs_for_line (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return build_line_table_tuple_from_pcs (py_line, pcs); @@ -287,27 +287,11 @@ ltpy_dealloc (PyObject *self) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_linetable (void) { - if (PyType_Ready (&linetable_object_type) < 0) + if (gdbpy_type_ready (&linetable_object_type) < 0) return -1; - if (PyType_Ready (&linetable_entry_object_type) < 0) + if (gdbpy_type_ready (&linetable_entry_object_type) < 0) return -1; - if (PyType_Ready (<py_iterator_object_type) < 0) - return -1; - - Py_INCREF (&linetable_object_type); - Py_INCREF (&linetable_entry_object_type); - Py_INCREF (<py_iterator_object_type); - - if (gdb_pymodule_addobject (gdb_module, "LineTable", - (PyObject *) &linetable_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "LineTableEntry", - (PyObject *) &linetable_entry_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "LineTableIterator", - (PyObject *) <py_iterator_object_type) < 0) + if (gdbpy_type_ready (<py_iterator_object_type) < 0) return -1; return 0; diff --git a/gdb/python/py-membuf.c b/gdb/python/py-membuf.c index af48d01..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. @@ -102,11 +102,7 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_membuf (void) { membuf_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&membuf_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Membuf", - (PyObject *) &membuf_object_type); + return gdbpy_type_ready (&membuf_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_membuf); diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c index bc95e86..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. @@ -86,7 +86,8 @@ py_ui_out::do_end (ui_out_type type) void py_ui_out::do_field_signed (int fldno, int width, ui_align align, - const char *fldname, LONGEST value) + const char *fldname, LONGEST value, + const ui_file_style &style) { if (m_error.has_value ()) return; @@ -142,6 +143,13 @@ gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw) if (n_args < 0) return nullptr; + if (n_args == 0) + { + PyErr_SetString (PyExc_TypeError, + _("gdb.execute_mi requires command argument")); + return nullptr; + } + for (Py_ssize_t i = 0; i < n_args; ++i) { /* Note this returns a borrowed reference. */ @@ -168,8 +176,7 @@ gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return nullptr; + return gdbpy_handle_gdb_exception (nullptr, except); } return uiout.result ().release (); diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index 54427d4..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. @@ -447,12 +447,7 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_micommands () { micmdpy_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&micmdpy_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "MICommand", - (PyObject *) &micmdpy_object_type) - < 0) + if (gdbpy_type_ready (&micmdpy_object_type) < 0) return -1; invoke_cst = PyUnicode_FromString ("invoke"); @@ -514,7 +509,16 @@ micmdpy_set_installed (PyObject *self, PyObject *newvalue, void *closure) { struct micmdpy_object *micmd_obj = (struct micmdpy_object *) self; - bool installed_p = PyObject_IsTrue (newvalue); + if (!PyBool_Check (newvalue)) + { + PyErr_Format (PyExc_TypeError, + _("gdb.MICommand.installed must be set to a bool, not %s"), + newvalue == Py_None ? "None" : Py_TYPE(newvalue)->tp_name); + return -1; + } + + bool installed_p = newvalue == Py_True; + if (installed_p == (micmd_obj->mi_command != nullptr)) return 0; @@ -574,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 60a1483..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. @@ -162,7 +162,7 @@ objfpy_get_build_id (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (build_id != NULL) @@ -183,7 +183,7 @@ objfpy_get_progspace (PyObject *self, void *closure) objfile_object *obj = (objfile_object *) self; if (obj->objfile) - return pspace_to_pspace_object (obj->objfile->pspace).release (); + return pspace_to_pspace_object (obj->objfile->pspace ()).release (); Py_RETURN_NONE; } @@ -453,7 +453,7 @@ objfpy_add_separate_debug_file (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -488,7 +488,7 @@ objfpy_lookup_global_symbol (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -523,7 +523,7 @@ objfpy_lookup_static_symbol (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -709,11 +709,7 @@ objfile_to_objfile_object (struct objfile *objfile) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_objfile (void) { - if (PyType_Ready (&objfile_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Objfile", - (PyObject *) &objfile_object_type); + return gdbpy_type_ready (&objfile_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_objfile); diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index 621f85d..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. @@ -26,6 +26,7 @@ #include "completer.h" #include "language.h" #include "arch-utils.h" +#include "py-color.h" /* Python parameter types as in PARM_CONSTANTS below. */ @@ -43,6 +44,7 @@ enum py_param_types param_zuinteger, param_zuinteger_unlimited, param_enum, + param_color, }; /* Translation from Python parameters to GDB variable types. Keep in the @@ -69,7 +71,8 @@ param_to_var[] = { var_integer }, { var_uinteger }, { var_pinteger, pinteger_unlimited_literals }, - { var_enum } + { var_enum }, + { var_color } }; /* Parameter constants and their values. */ @@ -90,6 +93,7 @@ static struct { { "PARAM_ZUINTEGER", param_zuinteger }, { "PARAM_ZUINTEGER_UNLIMITED", param_zuinteger_unlimited }, { "PARAM_ENUM", param_enum }, + { "PARAM_COLOR", param_color }, { NULL, 0 } }; @@ -114,6 +118,9 @@ union parmpy_variable /* Hold a string, for enums. */ const char *cstringval; + + /* Hold a color. */ + ui_file_style::color color; }; /* A GDB parameter. */ @@ -157,6 +164,8 @@ make_setting (parmpy_object *s) return setting (type, s->value.stringval); else if (var_type_uses<const char *> (type)) return setting (type, &s->value.cstringval); + else if (var_type_uses<ui_file_style::color> (s->type)) + return setting (s->type, &s->value.color); else gdb_assert_not_reached ("unhandled var type"); } @@ -248,6 +257,19 @@ set_parameter_value (parmpy_object *self, PyObject *value) break; } + case var_color: + { + if (gdbpy_is_color (value)) + self->value.color = gdbpy_get_color (value); + else + { + PyErr_SetString (PyExc_RuntimeError, + _("color argument must be a gdb.Color object.")); + return -1; + } + } + break; + case var_boolean: if (! PyBool_Check (value)) { @@ -473,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."))); @@ -707,6 +733,15 @@ add_setshow_generic (enum var_types type, const literal_def *extra_literals, get_show_value, set_list, show_list); break; + case var_color: + /* Initialize the value, just in case. */ + self->value.color = ui_file_style::NONE; + commands = add_setshow_color_cmd (cmd_name.get (), cmdclass, + &self->value.color, set_doc, + show_doc, help_doc, get_set_value, + get_show_value, set_list, show_list); + break; + default: gdb_assert_not_reached ("Unhandled parameter class."); } @@ -830,7 +865,8 @@ parmpy_init (PyObject *self, PyObject *args, PyObject *kwds) && parmclass != param_string && parmclass != param_string_noescape && parmclass != param_optional_filename && parmclass != param_filename && parmclass != param_zinteger && parmclass != param_zuinteger - && parmclass != param_zuinteger_unlimited && parmclass != param_enum) + && parmclass != param_zuinteger_unlimited && parmclass != param_enum + && parmclass != param_color) { PyErr_SetString (PyExc_RuntimeError, _("Invalid parameter class argument.")); @@ -854,7 +890,7 @@ parmpy_init (PyObject *self, PyObject *args, PyObject *kwds) extra_literals = param_to_var[parmclass].extra_literals; obj->type = type; obj->extra_literals = extra_literals; - memset (&obj->value, 0, sizeof (obj->value)); + obj->value = {}; /* zeros initialization */ if (var_type_uses<std::string> (obj->type)) obj->value.stringval = new std::string; @@ -872,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 @@ -885,8 +933,7 @@ parmpy_init (PyObject *self, PyObject *args, PyObject *kwds) catch (const gdb_exception &except) { Py_DECREF (self); - gdbpy_convert_exception (except); - return -1; + return gdbpy_handle_gdb_exception (-1, except); } return 0; @@ -901,6 +948,8 @@ parmpy_dealloc (PyObject *obj) if (var_type_uses<std::string> (parm_obj->type)) delete parm_obj->value.stringval; + else if (var_type_uses<ui_file_style::color> (parm_obj->type)) + parm_obj->value.color.~color(); } /* Initialize the 'parameters' module. */ @@ -910,7 +959,7 @@ gdbpy_initialize_parameters (void) int i; parmpy_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&parmpy_object_type) < 0) + if (gdbpy_type_ready (&parmpy_object_type) < 0) return -1; set_doc_cst = PyUnicode_FromString ("set_doc"); @@ -928,8 +977,7 @@ gdbpy_initialize_parameters (void) return -1; } - return gdb_pymodule_addobject (gdb_module, "Parameter", - (PyObject *) &parmpy_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_parameters); diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c index 368b3a3..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. @@ -836,10 +836,7 @@ PyTypeObject printer_object_type = static int gdbpy_initialize_prettyprint () { - if (PyType_Ready (&printer_object_type) < 0) - return -1; - return gdb_pymodule_addobject (gdb_module, "ValuePrinter", - (PyObject *) &printer_object_type); + return gdbpy_type_ready (&printer_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_prettyprint); diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c index 5bc0015..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. @@ -55,8 +55,8 @@ struct pspace_object /* The debug method list. */ PyObject *xmethods; - /* The missing debug handler list. */ - PyObject *missing_debug_handlers; + /* The missing file handler list. */ + PyObject *missing_file_handlers; }; extern PyTypeObject pspace_object_type @@ -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. */ @@ -166,7 +166,7 @@ pspy_dealloc (PyObject *self) Py_XDECREF (ps_self->frame_unwinders); Py_XDECREF (ps_self->type_printers); Py_XDECREF (ps_self->xmethods); - Py_XDECREF (ps_self->missing_debug_handlers); + Py_XDECREF (ps_self->missing_file_handlers); Py_TYPE (self)->tp_free (self); } @@ -202,8 +202,8 @@ pspy_initialize (pspace_object *self) if (self->xmethods == NULL) return 0; - self->missing_debug_handlers = PyList_New (0); - if (self->missing_debug_handlers == nullptr) + self->missing_file_handlers = PyList_New (0); + if (self->missing_file_handlers == nullptr) return 0; return 1; @@ -349,18 +349,18 @@ pspy_get_xmethods (PyObject *o, void *ignore) /* Return the list of missing debug handlers for this program space. */ static PyObject * -pspy_get_missing_debug_handlers (PyObject *o, void *ignore) +pspy_get_missing_file_handlers (PyObject *o, void *ignore) { pspace_object *self = (pspace_object *) o; - Py_INCREF (self->missing_debug_handlers); - return self->missing_debug_handlers; + Py_INCREF (self->missing_file_handlers); + return self->missing_file_handlers; } /* Set this program space's list of missing debug handlers to HANDLERS. */ static int -pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers, +pspy_set_missing_file_handlers (PyObject *o, PyObject *handlers, void *ignore) { pspace_object *self = (pspace_object *) o; @@ -380,9 +380,9 @@ pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers, } /* Take care in case the LHS and RHS are related somehow. */ - gdbpy_ref<> tmp (self->missing_debug_handlers); + gdbpy_ref<> tmp (self->missing_file_handlers); Py_INCREF (handlers); - self->missing_debug_handlers = handlers; + self->missing_file_handlers = handlers; return 0; } @@ -522,7 +522,7 @@ pspy_block_for_pc (PyObject *o, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (cust == NULL || cust->objfile () == NULL) @@ -564,7 +564,7 @@ pspy_find_pc_line (PyObject *o, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -747,11 +747,10 @@ gdbpy_initialize_pspace (void) gdb::observers::free_program_space.attach (gdbpy_free_program_space_event, "py-progspace"); - if (PyType_Ready (&pspace_object_type) < 0) + if (gdbpy_type_ready (&pspace_object_type) < 0) return -1; - return gdb_pymodule_addobject (gdb_module, "Progspace", - (PyObject *) &pspace_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_pspace); @@ -779,8 +778,8 @@ static gdb_PyGetSetDef pspace_getset[] = "Type printers.", NULL }, { "xmethods", pspy_get_xmethods, NULL, "Debug methods.", NULL }, - { "missing_debug_handlers", pspy_get_missing_debug_handlers, - pspy_set_missing_debug_handlers, "Missing debug handlers.", NULL }, + { "missing_file_handlers", pspy_get_missing_file_handlers, + pspy_set_missing_file_handlers, "Missing file handlers.", NULL }, { NULL } }; diff --git a/gdb/python/py-record-btrace.c b/gdb/python/py-record-btrace.c index 36454fc..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. @@ -44,7 +44,8 @@ struct btpy_list_object { /* Stride size. */ Py_ssize_t step; - /* Either &BTPY_CALL_TYPE or &RECPY_INSN_TYPE. */ + /* Either &recpy_func_type, &recpy_insn_type, &recpy_aux_type or + &recpy_gap_type. */ PyTypeObject* element_type; }; @@ -140,15 +141,21 @@ btrace_func_from_recpy_func (const PyObject * const pyobject) } /* Looks at the recorded item with the number NUMBER and create a - gdb.RecordInstruction or gdb.RecordGap object for it accordingly. */ + gdb.RecordInstruction, gdb.RecordGap or gdb.RecordAuxiliary object + for it accordingly. */ static PyObject * -btpy_insn_or_gap_new (thread_info *tinfo, Py_ssize_t number) +btpy_item_new (thread_info *tinfo, Py_ssize_t number) { btrace_insn_iterator iter; int err_code; - btrace_find_insn_by_number (&iter, &tinfo->btrace, number); + if (btrace_find_insn_by_number (&iter, &tinfo->btrace, number) == 0) + { + PyErr_Format (gdbpy_gdb_error, _("No such instruction.")); + return nullptr; + } + err_code = btrace_insn_get_error (&iter); if (err_code != 0) @@ -162,6 +169,12 @@ btpy_insn_or_gap_new (thread_info *tinfo, Py_ssize_t number) return recpy_gap_new (err_code, err_string, number); } + const struct btrace_insn *insn = btrace_insn_get (&iter); + gdb_assert (insn != nullptr); + + if (insn->iclass == BTRACE_INSN_AUX) + return recpy_aux_new (tinfo, RECORD_METHOD_BTRACE, number); + return recpy_insn_new (tinfo, RECORD_METHOD_BTRACE, number); } @@ -204,7 +217,7 @@ recpy_bt_insn_sal (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -275,7 +288,7 @@ recpy_bt_insn_data (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } object = PyBytes_FromStringAndSize ((const char *) buffer.data (), @@ -305,8 +318,7 @@ recpy_bt_insn_decoded (PyObject *self, void *closure) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } return PyBytes_FromString (strfile.string ().c_str ()); @@ -423,6 +435,48 @@ recpy_bt_func_next (PyObject *self, void *closure) RECORD_METHOD_BTRACE, func->next); } +/* Implementation of Auxiliary.data [str] for btrace. */ + +PyObject * +recpy_bt_aux_data (PyObject *self, void *closure) +{ + const btrace_insn *insn; + const recpy_element_object *obj; + thread_info *tinfo; + btrace_insn_iterator iter; + + if (Py_TYPE (self) != &recpy_aux_type) + { + PyErr_Format (gdbpy_gdb_error, _("Must be a gdb.Auxiliary.")); + return nullptr; + } + + obj = (const recpy_element_object *) self; + tinfo = obj->thread; + + if (tinfo == nullptr || btrace_is_empty (tinfo)) + { + PyErr_Format (gdbpy_gdb_error, _("No such auxiliary object.")); + return nullptr; + } + + if (btrace_find_insn_by_number (&iter, &tinfo->btrace, obj->number) == 0) + { + PyErr_Format (gdbpy_gdb_error, _("No such auxiliary object.")); + return nullptr; + } + + insn = btrace_insn_get (&iter); + if (insn == nullptr || insn->iclass != BTRACE_INSN_AUX) + { + PyErr_Format (gdbpy_gdb_error, _("Not a valid auxiliary object.")); + return nullptr; + } + + return PyUnicode_FromString + (iter.btinfo->aux_data.at (insn->aux_data_index).c_str ()); +} + /* Implementation of BtraceList.__len__ (self) -> int. */ static Py_ssize_t @@ -439,8 +493,9 @@ btpy_list_length (PyObject *self) } /* Implementation of - BtraceList.__getitem__ (self, key) -> BtraceInstruction and - BtraceList.__getitem__ (self, key) -> BtraceFunctionCall. */ + BtraceList.__getitem__ (self, key) -> BtraceInstruction, + BtraceList.__getitem__ (self, key) -> BtraceFunctionCall, + BtraceList.__getitem__ (self, key) -> BtraceAuxiliary. */ static PyObject * btpy_list_item (PyObject *self, Py_ssize_t index) @@ -454,10 +509,13 @@ btpy_list_item (PyObject *self, Py_ssize_t index) number = obj->first + (obj->step * index); - if (obj->element_type == &recpy_insn_type) - return recpy_insn_new (obj->thread, RECORD_METHOD_BTRACE, number); - else + if (obj->element_type == &recpy_func_type) return recpy_func_new (obj->thread, RECORD_METHOD_BTRACE, number); + else if (obj->element_type == &recpy_insn_type + || obj->element_type == &recpy_aux_type) + return btpy_item_new (obj->thread, number); + else + return PyErr_Format (gdbpy_gdb_error, _("Not a valid BtraceList object.")); } /* Implementation of BtraceList.__getitem__ (self, slice) -> BtraceList. */ @@ -644,8 +702,7 @@ recpy_bt_replay_position (PyObject *self, void *closure) if (tinfo->btrace.replay == NULL) Py_RETURN_NONE; - return btpy_insn_or_gap_new (tinfo, - btrace_insn_number (tinfo->btrace.replay)); + return btpy_item_new (tinfo, btrace_insn_number (tinfo->btrace.replay)); } /* Implementation of @@ -667,7 +724,7 @@ recpy_bt_begin (PyObject *self, void *closure) Py_RETURN_NONE; btrace_insn_begin (&iterator, &tinfo->btrace); - return btpy_insn_or_gap_new (tinfo, btrace_insn_number (&iterator)); + return btpy_item_new (tinfo, btrace_insn_number (&iterator)); } /* Implementation of @@ -689,7 +746,7 @@ recpy_bt_end (PyObject *self, void *closure) Py_RETURN_NONE; btrace_insn_end (&iterator, &tinfo->btrace); - return btpy_insn_or_gap_new (tinfo, btrace_insn_number (&iterator)); + return btpy_item_new (tinfo, btrace_insn_number (&iterator)); } /* Implementation of @@ -750,6 +807,109 @@ recpy_bt_function_call_history (PyObject *self, void *closure) return btpy_list_new (tinfo, first, last, 1, &recpy_func_type); } +/* Helper function that calls PTW_FILTER with PAYLOAD and IP as arguments. + Returns the string that will be printed, if there is a filter to call. */ +static std::optional<std::string> +recpy_call_filter (const uint64_t payload, std::optional<uint64_t> ip, + const void *ptw_filter) +{ + std::optional<std::string> result; + + gdb_assert (ptw_filter != nullptr); + if ((PyObject *) ptw_filter == Py_None) + return result; + + gdbpy_enter enter_py; + + gdbpy_ref<> py_payload = gdb_py_object_from_ulongest (payload); + + gdbpy_ref<> py_ip; + if (!ip.has_value ()) + py_ip = gdbpy_ref<>::new_reference (Py_None); + else + py_ip = gdb_py_object_from_ulongest (*ip); + + gdbpy_ref<> py_result (PyObject_CallFunctionObjArgs ((PyObject *) ptw_filter, + py_payload.get (), + py_ip.get (), + nullptr)); + + if (py_result == nullptr) + { + gdbpy_print_stack (); + gdbpy_error (_("Couldn't call the ptwrite filter.")); + } + + /* Py_None is valid and results in no output. */ + if (py_result == Py_None) + { + result = ""; + return result; + } + + gdb::unique_xmalloc_ptr<char> user_string + = gdbpy_obj_to_string (py_result.get ()); + + if (user_string == nullptr) + { + gdbpy_print_stack (); + gdbpy_error (_("The ptwrite filter didn't return a string.")); + } + else + result = user_string.get (); + + return result; +} + +/* Helper function returning the current ptwrite filter. */ + +static PyObject * +get_ptwrite_filter () +{ + gdbpy_ref<> module (PyImport_ImportModule ("gdb.ptwrite")); + + if (PyErr_Occurred ()) + { + gdbpy_print_stack (); + gdbpy_error (_("Couldn't import gdb.ptwrite.")); + } + + /* We need to keep the reference count. */ + gdbpy_ref<> ptw_filter (gdbpy_call_method (module.get (), "get_filter")); + + if (PyErr_Occurred ()) + { + gdbpy_print_stack (); + gdbpy_error (_("Couldn't get the ptwrite filter.")); + } + + return ptw_filter.get(); +} + +/* Used for registering any python ptwrite filter to the current thread. A + pointer to this function is stored in the python extension interface. */ + +void +gdbpy_load_ptwrite_filter (const struct extension_language_defn *extlang, + struct btrace_thread_info *btinfo) +{ + gdb_assert (btinfo != nullptr); + + gdbpy_enter enter_py; + + btinfo->ptw_context = get_ptwrite_filter (); + +#if defined (HAVE_STRUCT_PT_EVENT_VARIANT_PTWRITE) + if (!btinfo->target->conf.pt.ptwrite && btinfo->ptw_context != Py_None) + warning (_("The target doesn't support decoding ptwrite events.")); +#else + if (btinfo->ptw_context != Py_None) + warning (_("Libipt doesn't support decoding ptwrite events.")); +#endif /* defined (HAVE_STRUCT_PT_EVENT_VARIANT_PTWRITE) */ + + btinfo->ptw_callback_fun = &recpy_call_filter; +} + /* Implementation of BtraceRecord.goto (self, BtraceInstruction) -> None. */ PyObject * @@ -783,12 +943,25 @@ recpy_bt_goto (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; } +/* Implementation of BtraceRecord.clear (self) -> None. */ + +PyObject * +recpy_bt_clear (PyObject *self, PyObject *args) +{ + const recpy_record_object * const record = (recpy_record_object *) self; + thread_info *const tinfo = record->thread; + + btrace_clear (tinfo); + + Py_RETURN_NONE; +} + /* BtraceList methods. */ static PyMethodDef btpy_list_methods[] = @@ -833,7 +1006,7 @@ gdbpy_initialize_btrace (void) btpy_list_mapping_methods.mp_subscript = btpy_list_slice; - return PyType_Ready (&btpy_list_type); + return gdbpy_type_ready (&btpy_list_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_btrace); diff --git a/gdb/python/py-record-btrace.h b/gdb/python/py-record-btrace.h index 8678e77..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. @@ -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_RECORD_BTRACE_H -#define PYTHON_PY_RECORD_BTRACE_H +#ifndef GDB_PYTHON_PY_RECORD_BTRACE_H +#define GDB_PYTHON_PY_RECORD_BTRACE_H #include "python-internal.h" @@ -31,6 +31,9 @@ extern PyObject *recpy_bt_format (PyObject *self, void *closure); /* Implementation of record.goto (instruction) -> None. */ extern PyObject *recpy_bt_goto (PyObject *self, PyObject *value); +/* Implementation of BtraceRecord.clear (self) -> None. */ +extern PyObject *recpy_bt_clear (PyObject *self, PyObject *args); + /* Implementation of record.instruction_history [list]. */ extern PyObject *recpy_bt_instruction_history (PyObject *self, void *closure); @@ -88,4 +91,7 @@ extern PyObject *recpy_bt_func_prev (PyObject *self, void *closure); /* Implementation of RecordFunctionSegment.next [RecordFunctionSegment]. */ extern PyObject *recpy_bt_func_next (PyObject *self, void *closure); -#endif /* PYTHON_PY_RECORD_BTRACE_H */ +/* Implementation of RecordAuxiliary.decoded [str]. */ +extern PyObject *recpy_bt_aux_data (PyObject *self, void *closure); + +#endif /* GDB_PYTHON_PY_RECORD_BTRACE_H */ 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 7125e4e..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. @@ -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_RECORD_FULL_H -#define PYTHON_PY_RECORD_FULL_H +#ifndef GDB_PYTHON_PY_RECORD_FULL_H +#define GDB_PYTHON_PY_RECORD_FULL_H #include "python-internal.h" @@ -28,4 +28,4 @@ extern PyObject *recpy_full_method (PyObject *self, void *value); /* Implementation of record.format [str]. */ extern PyObject *recpy_full_format (PyObject *self, void *value); -#endif /* PYTHON_PY_RECORD_FULL_H */ +#endif /* GDB_PYTHON_PY_RECORD_FULL_H */ diff --git a/gdb/python/py-record.c b/gdb/python/py-record.c index d489126..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. @@ -48,6 +48,12 @@ static PyTypeObject recpy_gap_type = { PyVarObject_HEAD_INIT (NULL, 0) }; +/* Python RecordAuxiliary type. */ + +PyTypeObject recpy_aux_type = { + PyVarObject_HEAD_INIT (nullptr, 0) +}; + /* Python RecordGap object. */ struct recpy_gap_object { @@ -108,6 +114,19 @@ recpy_goto (PyObject *self, PyObject *value) return PyErr_Format (PyExc_NotImplementedError, _("Not implemented.")); } +/* Implementation of record.clear () -> None. */ + +static PyObject * +recpy_clear (PyObject *self, PyObject *value) +{ + const recpy_record_object * const obj = (recpy_record_object *) self; + + if (obj->method == RECORD_METHOD_BTRACE) + return recpy_bt_clear (self, value); + + return PyErr_Format (PyExc_NotImplementedError, _("Not implemented.")); +} + /* Implementation of record.replay_position [instruction] */ static PyObject * @@ -389,8 +408,8 @@ recpy_element_hash (PyObject *self) return obj->number; } -/* Implementation of operator == and != of RecordInstruction and - RecordFunctionSegment. */ +/* Implementation of operator == and != of RecordInstruction, + RecordFunctionSegment and RecordAuxiliary. */ static PyObject * recpy_element_richcompare (PyObject *self, PyObject *other, int op) @@ -478,12 +497,47 @@ recpy_gap_reason_string (PyObject *self, void *closure) return PyUnicode_FromString (obj->reason_string); } +/* Create a new gdb.Auxiliary object. */ + +PyObject * +recpy_aux_new (thread_info *thread, enum record_method method, + Py_ssize_t number) +{ + recpy_element_object * const obj = PyObject_New (recpy_element_object, + &recpy_aux_type); + + if (obj == NULL) + return NULL; + + obj->thread = thread; + obj->method = method; + obj->number = number; + + return (PyObject *) obj; +} + +/* Implementation of Auxiliary.data [buffer]. */ + +static PyObject * +recpy_aux_data (PyObject *self, void *closure) +{ + const recpy_element_object * const obj = (recpy_element_object *) self; + + if (obj->method == RECORD_METHOD_BTRACE) + return recpy_bt_aux_data (self, closure); + + return PyErr_Format (PyExc_NotImplementedError, _("Not implemented.")); +} + /* Record method list. */ static PyMethodDef recpy_record_methods[] = { { "goto", recpy_goto, METH_VARARGS, "goto (instruction|function_call) -> None.\n\ Rewind to given location."}, + { "clear", recpy_clear, METH_VARARGS, + "clear () -> None.\n\ +Clears the trace."}, { NULL } }; @@ -543,6 +597,14 @@ static gdb_PyGetSetDef recpy_gap_getset[] = { { NULL } }; +/* RecordAuxiliary member list. */ + +static gdb_PyGetSetDef recpy_aux_getset[] = { + { "number", recpy_element_number, nullptr, "element number", nullptr}, + { "data", recpy_aux_data, nullptr, "data", nullptr}, + { nullptr } +}; + /* Sets up the record API in the gdb module. */ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION @@ -582,10 +644,20 @@ gdbpy_initialize_record (void) recpy_gap_type.tp_doc = "GDB recorded gap object"; recpy_gap_type.tp_getset = recpy_gap_getset; - if (PyType_Ready (&recpy_record_type) < 0 - || PyType_Ready (&recpy_insn_type) < 0 - || PyType_Ready (&recpy_func_type) < 0 - || PyType_Ready (&recpy_gap_type) < 0) + recpy_aux_type.tp_new = PyType_GenericNew; + recpy_aux_type.tp_flags = Py_TPFLAGS_DEFAULT; + recpy_aux_type.tp_basicsize = sizeof (recpy_element_object); + recpy_aux_type.tp_name = "gdb.RecordAuxiliary"; + recpy_aux_type.tp_doc = "GDB recorded auxiliary object"; + recpy_aux_type.tp_getset = recpy_aux_getset; + recpy_aux_type.tp_richcompare = recpy_element_richcompare; + recpy_aux_type.tp_hash = recpy_element_hash; + + if (gdbpy_type_ready (&recpy_record_type) < 0 + || gdbpy_type_ready (&recpy_insn_type) < 0 + || gdbpy_type_ready (&recpy_func_type) < 0 + || gdbpy_type_ready (&recpy_gap_type) < 0 + || gdbpy_type_ready (&recpy_aux_type) < 0) return -1; else return 0; @@ -598,7 +670,6 @@ gdbpy_start_recording (PyObject *self, PyObject *args) { const char *method = NULL; const char *format = NULL; - PyObject *ret = NULL; if (!PyArg_ParseTuple (args, "|ss", &method, &format)) return NULL; @@ -606,14 +677,12 @@ gdbpy_start_recording (PyObject *self, PyObject *args) try { record_start (method, format, 0); - ret = gdbpy_current_recording (self, args); + return gdbpy_current_recording (self, args); } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); + return gdbpy_handle_gdb_exception (nullptr, except); } - - return ret; } /* Implementation of gdb.current_recording (self) -> gdb.Record. */ @@ -644,7 +713,7 @@ gdbpy_stop_recording (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; diff --git a/gdb/python/py-record.h b/gdb/python/py-record.h index 7e11610..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. @@ -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_RECORD_H -#define PYTHON_PY_RECORD_H +#ifndef GDB_PYTHON_PY_RECORD_H +#define GDB_PYTHON_PY_RECORD_H #include "inferior.h" #include "python-internal.h" @@ -59,6 +59,9 @@ extern PyTypeObject recpy_insn_type; /* Python RecordFunctionSegment type. */ extern PyTypeObject recpy_func_type; +/* Python RecordAuxiliary type. */ +extern PyTypeObject recpy_aux_type; + /* Create a new gdb.RecordInstruction object. */ extern PyObject *recpy_insn_new (thread_info *thread, enum record_method method, Py_ssize_t number); @@ -71,4 +74,8 @@ extern PyObject *recpy_func_new (thread_info *thread, enum record_method method, extern PyObject *recpy_gap_new (int reason_code, const char *reason_string, Py_ssize_t number); -#endif /* PYTHON_PY_RECORD_H */ +/* Create a new gdb.RecordGap object. */ +extern PyObject *recpy_aux_new (thread_info *thread, enum record_method method, + Py_ssize_t number); + +#endif /* GDB_PYTHON_PY_RECORD_H */ diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h index 35029e8..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. @@ -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_REF_H -#define PYTHON_PY_REF_H +#ifndef GDB_PYTHON_PY_REF_H +#define GDB_PYTHON_PY_REF_H #include "gdbsupport/gdb_ref_ptr.h" @@ -42,4 +42,4 @@ struct gdbpy_ref_policy template<typename T = PyObject> using gdbpy_ref = gdb::ref_ptr<T, gdbpy_ref_policy<T>>; -#endif /* PYTHON_PY_REF_H */ +#endif /* GDB_PYTHON_PY_REF_H */ diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c index f03274c..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; @@ -430,35 +429,22 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_registers () { register_descriptor_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (®ister_descriptor_object_type) < 0) - return -1; - if (gdb_pymodule_addobject - (gdb_module, "RegisterDescriptor", - (PyObject *) ®ister_descriptor_object_type) < 0) + if (gdbpy_type_ready (®ister_descriptor_object_type) < 0) return -1; reggroup_iterator_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (®group_iterator_object_type) < 0) - return -1; - if (gdb_pymodule_addobject - (gdb_module, "RegisterGroupsIterator", - (PyObject *) ®group_iterator_object_type) < 0) + if (gdbpy_type_ready (®group_iterator_object_type) < 0) return -1; reggroup_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (®group_object_type) < 0) - return -1; - if (gdb_pymodule_addobject - (gdb_module, "RegisterGroup", - (PyObject *) ®group_object_type) < 0) + if (gdbpy_type_ready (®group_object_type) < 0) return -1; register_descriptor_iterator_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (®ister_descriptor_iterator_object_type) < 0) + if (gdbpy_type_ready (®ister_descriptor_iterator_object_type) < 0) return -1; - return (gdb_pymodule_addobject - (gdb_module, "RegisterDescriptorIterator", - (PyObject *) ®ister_descriptor_iterator_object_type)); + + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_registers); 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 be26bc1..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. @@ -74,8 +74,7 @@ py_print_bpstat (bpstat *bs, enum gdb_signal stop_signal) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return nullptr; + return gdbpy_handle_gdb_exception (nullptr, except); } gdbpy_ref<> dict = uiout.result (); diff --git a/gdb/python/py-stopevent.h b/gdb/python/py-stopevent.h index 6cae0a7..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. @@ -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_STOPEVENT_H -#define PYTHON_PY_STOPEVENT_H +#ifndef GDB_PYTHON_PY_STOPEVENT_H +#define GDB_PYTHON_PY_STOPEVENT_H #include "py-event.h" @@ -35,4 +35,4 @@ extern gdbpy_ref<> create_breakpoint_event_object (const gdbpy_ref<> &dict, extern gdbpy_ref<> create_signal_event_object (const gdbpy_ref<> &dict, enum gdb_signal stop_signal); -#endif /* PYTHON_PY_STOPEVENT_H */ +#endif /* GDB_PYTHON_PY_STOPEVENT_H */ diff --git a/gdb/python/py-symbol.c b/gdb/python/py-symbol.c index 754420f..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) { @@ -205,6 +194,18 @@ sympy_is_variable (PyObject *self, void *closure) || theclass == LOC_OPTIMIZED_OUT)); } +/* Implementation of Symbol.is_artificial. */ + +static PyObject * +sympy_is_artificial (PyObject *self, void *closure) +{ + struct symbol *symbol = nullptr; + + SYMPY_REQUIRE_VALID (self, symbol); + + return PyBool_FromLong (symbol->is_artificial ()); +} + /* Implementation of gdb.Symbol.needs_frame -> Boolean. Returns true iff the symbol needs a frame for evaluation. */ @@ -222,7 +223,7 @@ sympy_needs_frame (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (result) @@ -307,7 +308,7 @@ sympy_value (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -322,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 @@ -344,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); @@ -365,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); } @@ -425,7 +433,7 @@ gdbpy_lookup_symbol (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } } @@ -436,7 +444,7 @@ gdbpy_lookup_symbol (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } gdbpy_ref<> ret_tuple (PyTuple_New (2)); @@ -485,7 +493,7 @@ gdbpy_lookup_global_symbol (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (symbol) @@ -553,7 +561,7 @@ gdbpy_lookup_static_symbol (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (symbol) @@ -598,8 +606,7 @@ gdbpy_lookup_static_symbols (PyObject *self, PyObject *args, PyObject *kw) /* Expand any symtabs that contain potentially matching symbols. */ lookup_name_info lookup_name (name, symbol_name_match_type::FULL); expand_symtabs_matching (NULL, lookup_name, NULL, NULL, - SEARCH_GLOBAL_BLOCK | SEARCH_STATIC_BLOCK, - SEARCH_ALL_DOMAINS); + SEARCH_STATIC_BLOCK, flags); for (objfile *objfile : current_program_space->objfiles ()) { @@ -620,10 +627,11 @@ gdbpy_lookup_static_symbols (PyObject *self, PyObject *args, PyObject *kw) if (symbol != nullptr) { - PyObject *sym_obj - = symbol_to_symbol_object (symbol); + PyObject *sym_obj = symbol_to_symbol_object (symbol); + if (sym_obj == nullptr) + return nullptr; if (PyList_Append (return_list.get (), sym_obj) == -1) - return NULL; + return nullptr; } } } @@ -631,7 +639,7 @@ gdbpy_lookup_static_symbols (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return return_list.release (); @@ -640,7 +648,7 @@ gdbpy_lookup_static_symbols (PyObject *self, PyObject *args, PyObject *kw) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_symbols (void) { - if (PyType_Ready (&symbol_object_type) < 0) + if (gdbpy_type_ready (&symbol_object_type) < 0) return -1; if (PyModule_AddIntConstant (gdb_module, "SYMBOL_LOC_UNDEF", LOC_UNDEF) < 0 @@ -685,8 +693,7 @@ gdbpy_initialize_symbols (void) #include "sym-domains.def" #undef SYM_DOMAIN - return gdb_pymodule_addobject (gdb_module, "Symbol", - (PyObject *) &symbol_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_symbols); @@ -708,8 +715,11 @@ 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, + "True if the symbol is marked artificial." }, { "is_constant", sympy_is_constant, NULL, "True if the symbol is a constant." }, { "is_function", sympy_is_function, NULL, diff --git a/gdb/python/py-symtab.c b/gdb/python/py-symtab.c index 7290b85..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->sal != nullptr && self_sal->sal->symtab != nullptr) + salpy_registry.remove (self_sal->sal->symtab->compunit ()->objfile (), + self_sal); - if (self_sal->next) - self_sal->next->prev = self_sal->prev; - - 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 @@ -512,19 +427,14 @@ static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_symtabs (void) { symtab_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&symtab_object_type) < 0) + if (gdbpy_type_ready (&symtab_object_type) < 0) return -1; sal_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&sal_object_type) < 0) + if (gdbpy_type_ready (&sal_object_type) < 0) return -1; - if (gdb_pymodule_addobject (gdb_module, "Symtab", - (PyObject *) &symtab_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Symtab_and_line", - (PyObject *) &sal_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_symtabs); @@ -565,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 984fa9b..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. @@ -36,6 +36,9 @@ #include "tui/tui-layout.h" #include "tui/tui-wingeneral.h" #include "tui/tui-winsource.h" +#include "observable.h" +#include "py-events.h" +#include "py-event.h" class tui_py_window; @@ -95,7 +98,7 @@ public: { wnoutrefresh (handle.get ()); touchwin (m_inner_window.get ()); - tui_wrefresh (m_inner_window.get ()); + wnoutrefresh (m_inner_window.get ()); } else tui_win_info::refresh_window (); @@ -180,6 +183,8 @@ tui_py_window::~tui_py_window () void tui_py_window::rerender () { + tui_batch_rendering batch; + tui_win_info::rerender (); gdbpy_enter enter_py; @@ -206,6 +211,8 @@ tui_py_window::rerender () void tui_py_window::do_scroll_horizontal (int num_to_scroll) { + tui_batch_rendering batch; + gdbpy_enter enter_py; if (PyObject_HasAttrString (m_window.get (), "hscroll")) @@ -220,6 +227,8 @@ tui_py_window::do_scroll_horizontal (int num_to_scroll) void tui_py_window::do_scroll_vertical (int num_to_scroll) { + tui_batch_rendering batch; + gdbpy_enter enter_py; if (PyObject_HasAttrString (m_window.get (), "vscroll")) @@ -242,6 +251,8 @@ tui_py_window::resize (int height_, int width_, int origin_x_, int origin_y_) void tui_py_window::click (int mouse_x, int mouse_y, int mouse_button) { + tui_batch_rendering batch; + gdbpy_enter enter_py; if (PyObject_HasAttrString (m_window.get (), "click")) @@ -258,6 +269,8 @@ tui_py_window::output (const char *text, bool full_window) { if (m_inner_window != nullptr) { + tui_batch_rendering batch; + if (full_window) werase (m_inner_window.get ()); @@ -265,7 +278,7 @@ tui_py_window::output (const char *text, bool full_window) if (full_window) check_and_display_highlight_if_needed (); else - tui_wrefresh (m_inner_window.get ()); + wnoutrefresh (m_inner_window.get ()); } } @@ -415,8 +428,7 @@ gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return nullptr; + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -607,6 +619,29 @@ PyTypeObject gdbpy_tui_window_object_type = 0, /* tp_alloc */ }; +/* Called when TUI is enabled or disabled. */ + +static void +gdbpy_tui_enabled (bool state) +{ + gdbpy_enter enter_py; + + if (evregpy_no_listeners_p (gdb_py_events.tui_enabled)) + return; + + gdbpy_ref<> event_obj = create_event_object (&tui_enabled_event_object_type); + if (event_obj == nullptr) + { + gdbpy_print_stack (); + return; + } + + gdbpy_ref<> code (PyBool_FromLong (state)); + if (evpy_add_attribute (event_obj.get (), "enabled", code.get ()) < 0 + || evpy_emit_event (event_obj.get (), gdb_py_events.tui_enabled) < 0) + gdbpy_print_stack (); +} + #endif /* TUI */ /* Initialize this module. */ @@ -616,8 +651,10 @@ gdbpy_initialize_tui () { #ifdef TUI gdbpy_tui_window_object_type.tp_new = PyType_GenericNew; - if (PyType_Ready (&gdbpy_tui_window_object_type) < 0) + if (gdbpy_type_ready (&gdbpy_tui_window_object_type) < 0) return -1; + + gdb::observers::tui_enabled.attach (gdbpy_tui_enabled, "py-tui"); #endif /* TUI */ return 0; diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c index 863e6f6..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 ()); @@ -292,7 +286,7 @@ typy_fields_items (PyObject *self, enum gdbpy_iter_kind kind) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } gdbpy_ref<> type_holder; @@ -456,7 +450,7 @@ typy_is_array_like (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (result) @@ -480,7 +474,7 @@ typy_is_string_like (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (result) @@ -501,7 +495,7 @@ typy_strip_typedefs (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (type); @@ -522,7 +516,7 @@ typy_get_composite (struct type *type) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (!type->is_pointer_or_reference ()) @@ -592,7 +586,7 @@ typy_array_1 (PyObject *self, PyObject *args, int is_vector) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (array); @@ -626,7 +620,7 @@ typy_pointer (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (type); @@ -698,7 +692,7 @@ typy_reference (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (type); @@ -732,7 +726,7 @@ typy_const (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (type); @@ -750,7 +744,7 @@ typy_volatile (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (type); @@ -768,7 +762,7 @@ typy_unqualified (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type_to_type_object (type); @@ -859,7 +853,7 @@ typy_lookup_typename (const char *type_name, const struct block *block) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return type; @@ -913,7 +907,7 @@ typy_lookup_type (struct demangle_component *demangled, } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } } @@ -955,7 +949,7 @@ typy_legacy_template_argument (struct type *type, const struct block *block, } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (! info) @@ -1034,7 +1028,7 @@ typy_template_argument (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } /* We might not have DW_TAG_template_*, so try to parse the type's @@ -1069,7 +1063,7 @@ typy_template_argument (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -1093,7 +1087,7 @@ typy_repr (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } auto py_typename = PyUnicode_Decode (type_name.c_str (), type_name.size (), host_charset (), NULL); @@ -1115,7 +1109,7 @@ typy_str (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_Decode (thetype.c_str (), thetype.size (), @@ -1151,7 +1145,7 @@ typy_richcompare (PyObject *self, PyObject *other, int op) { /* If there is a GDB exception, a comparison is not capable (or trusted), so exit. */ - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } } @@ -1162,76 +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; - - htab_up copied_types = create_copied_types_hash (); - - while (obj) + if (obj->type->is_objfile_owned ()) { - type_object *next = obj->next; - - htab_empty (copied_types.get ()); - - obj->type = copy_type_recursive (obj->type, copied_types.get ()); + copied_types_hash_t 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). */ @@ -1465,10 +1444,24 @@ type_to_type_object (struct type *type) if (type->is_stub ()) type = check_typedef (type); } - catch (...) + catch (const gdb_exception_error &) { /* Just ignore failures in check_typedef. */ } + catch (const gdb_exception &except) + { + 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) @@ -1522,11 +1515,11 @@ gdbpy_lookup_type (PyObject *self, PyObject *args, PyObject *kw) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_types (void) { - if (PyType_Ready (&type_object_type) < 0) + if (gdbpy_type_ready (&type_object_type) < 0) return -1; - if (PyType_Ready (&field_object_type) < 0) + if (gdbpy_type_ready (&field_object_type) < 0) return -1; - if (PyType_Ready (&type_iterator_object_type) < 0) + if (gdbpy_type_ready (&type_iterator_object_type) < 0) return -1; for (const auto &item : pyty_codes) @@ -1535,16 +1528,7 @@ gdbpy_initialize_types (void) return -1; } - if (gdb_pymodule_addobject (gdb_module, "Type", - (PyObject *) &type_object_type) < 0) - return -1; - - if (gdb_pymodule_addobject (gdb_module, "TypeIterator", - (PyObject *) &type_iterator_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Field", - (PyObject *) &field_object_type); + return 0; } GDBPY_INITIALIZE_FILE (gdbpy_initialize_types); @@ -1690,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 a2fc90e..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. @@ -86,7 +86,8 @@ protected: void do_end (ui_out_type type) override; void do_field_signed (int fldno, int width, ui_align align, - const char *fldname, LONGEST value) override; + const char *fldname, LONGEST value, + const ui_file_style &style) override; void do_field_unsigned (int fldno, int width, ui_align align, const char *fldname, ULONGEST value) override; diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c index e36768e..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. @@ -228,7 +228,7 @@ unwind_infopy_str (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } } else @@ -378,7 +378,7 @@ unwind_infopy_add_saved_register (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } gdbpy_ref<> new_value = gdbpy_ref<>::new_reference (pyo_reg_value); @@ -429,7 +429,7 @@ pending_framepy_str (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_FromFormat ("SP=%s,PC=%s", sp_str, pc_str); @@ -456,7 +456,7 @@ pending_framepy_repr (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_FromFormat ("<%s level=%d, sp=%s, pc=%s>", @@ -505,7 +505,7 @@ pending_framepy_read_register (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -546,7 +546,7 @@ pending_framepy_name (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (name != nullptr) @@ -574,7 +574,7 @@ pending_framepy_pc (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return gdb_py_object_from_ulongest (pc).release (); @@ -601,7 +601,7 @@ pending_framepy_language (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -628,7 +628,7 @@ pending_framepy_find_sal (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return sal_obj; @@ -653,7 +653,7 @@ pending_framepy_block (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } for (fn_block = block; @@ -696,7 +696,7 @@ pending_framepy_function (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (sym != nullptr) @@ -785,11 +785,35 @@ pending_framepy_level (PyObject *self, PyObject *args) return gdb_py_object_from_longest (level).release (); } +/* Class for frame unwinders registered by the Python architecture callback. */ +class frame_unwind_python : public frame_unwind +{ +public: + frame_unwind_python (const struct frame_data *newarch) + : frame_unwind ("python", NORMAL_FRAME, FRAME_UNWIND_EXTENSION, newarch) + { } + + /* No need to override stop_reason, we want the default. */ + + int sniff (const frame_info_ptr &this_frame, + void **this_prologue_cache) const override; + + void this_id (const frame_info_ptr &this_frame, void **this_prologue_cache, + struct frame_id *id) const override; + + struct value *prev_register (const frame_info_ptr &this_frame, + void **this_prologue_cache, + int regnum) const override; + + void dealloc_cache (frame_info *self, void *this_cache) const override; +}; + /* frame_unwind.this_id method. */ -static void -pyuw_this_id (const frame_info_ptr &this_frame, void **cache_ptr, - struct frame_id *this_id) +void +frame_unwind_python::this_id (const frame_info_ptr &this_frame, + void **cache_ptr, + struct frame_id *this_id) const { *this_id = ((cached_frame_info *) *cache_ptr)->frame_id; pyuw_debug_printf ("frame_id: %s", this_id->to_string ().c_str ()); @@ -797,9 +821,9 @@ pyuw_this_id (const frame_info_ptr &this_frame, void **cache_ptr, /* frame_unwind.prev_register. */ -static struct value * -pyuw_prev_register (const frame_info_ptr &this_frame, void **cache_ptr, - int regnum) +struct value * +frame_unwind_python::prev_register (const frame_info_ptr &this_frame, + void **cache_ptr, int regnum) const { PYUW_SCOPED_DEBUG_ENTER_EXIT; @@ -812,7 +836,7 @@ pyuw_prev_register (const frame_info_ptr &this_frame, void **cache_ptr, for (; reg_info < reg_info_end; ++reg_info) { if (regnum == reg_info->num) - return frame_unwind_got_bytes (this_frame, regnum, reg_info->data.get ()); + return frame_unwind_got_bytes (this_frame, regnum, reg_info->data); } return frame_unwind_got_optimized (this_frame, regnum); @@ -820,13 +844,13 @@ pyuw_prev_register (const frame_info_ptr &this_frame, void **cache_ptr, /* Frame sniffer dispatch. */ -static int -pyuw_sniffer (const struct frame_unwind *self, const frame_info_ptr &this_frame, - void **cache_ptr) +int +frame_unwind_python::sniff (const frame_info_ptr &this_frame, + void **cache_ptr) const { PYUW_SCOPED_DEBUG_ENTER_EXIT; - struct gdbarch *gdbarch = (struct gdbarch *) (self->unwind_data); + struct gdbarch *gdbarch = (struct gdbarch *) (this->unwind_data ()); cached_frame_info *cached_frame; gdbpy_enter enter_py (gdbarch); @@ -905,9 +929,9 @@ pyuw_sniffer (const struct frame_unwind *self, 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 = @@ -936,8 +960,9 @@ pyuw_sniffer (const struct frame_unwind *self, const frame_info_ptr &this_frame, cached_reg_t *cached = new (&cached_frame->reg[i]) cached_reg_t (); cached->num = reg->number; - cached->data.reset ((gdb_byte *) xmalloc (data_size)); - memcpy (cached->data.get (), value->contents ().data (), data_size); + cached->data.resize (data_size); + gdb::array_view<const gdb_byte> contents = value->contents (); + cached->data.assign (contents.begin (), contents.end ()); } } @@ -947,8 +972,8 @@ pyuw_sniffer (const struct frame_unwind *self, const frame_info_ptr &this_frame, /* Frame cache release shim. */ -static void -pyuw_dealloc_cache (frame_info *this_frame, void *cache) +void +frame_unwind_python::dealloc_cache (frame_info *this_frame, void *cache) const { PYUW_SCOPED_DEBUG_ENTER_EXIT; cached_frame_info *cached_frame = (cached_frame_info *) cache; @@ -980,16 +1005,9 @@ pyuw_on_new_gdbarch (gdbarch *newarch) if (!data->unwinder_registered) { struct frame_unwind *unwinder - = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind); - - unwinder->name = "python"; - unwinder->type = NORMAL_FRAME; - unwinder->stop_reason = default_frame_unwind_stop_reason; - unwinder->this_id = pyuw_this_id; - unwinder->prev_register = pyuw_prev_register; - unwinder->unwind_data = (const struct frame_data *) newarch; - unwinder->sniffer = pyuw_sniffer; - unwinder->dealloc_cache = pyuw_dealloc_cache; + = obstack_new<frame_unwind_python> + (gdbarch_obstack (newarch), (const struct frame_data *) newarch); + frame_unwind_prepend_unwinder (newarch, unwinder); data->unwinder_registered = 1; } @@ -1002,22 +1020,16 @@ gdbpy_initialize_unwind (void) { gdb::observers::new_architecture.attach (pyuw_on_new_gdbarch, "py-unwind"); - if (PyType_Ready (&pending_frame_object_type) < 0) + if (gdbpy_type_ready (&pending_frame_object_type) < 0) return -1; - int rc = gdb_pymodule_addobject (gdb_module, "PendingFrame", - (PyObject *) &pending_frame_object_type); - if (rc != 0) - return rc; - if (PyType_Ready (&unwind_info_object_type) < 0) + if (gdbpy_type_ready (&unwind_info_object_type) < 0) return -1; - return gdb_pymodule_addobject (gdb_module, "UnwindInfo", - (PyObject *) &unwind_info_object_type); + + 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 47f65f4..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. @@ -247,7 +247,7 @@ get_addr_from_python (PyObject *obj, CORE_ADDR *addr) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } } else @@ -390,6 +390,35 @@ gdbpy_handle_exception () if (fetched_error.type_matches (PyExc_KeyboardInterrupt)) throw_quit ("Quit"); + else if (fetched_error.type_matches (PyExc_SystemExit)) + { + gdbpy_ref<> value = fetched_error.value (); + gdbpy_ref<> code (PyObject_GetAttrString (value.get (), "code")); + int exit_arg; + + if (code.get () == Py_None) + { + /* CODE == None: exit status is 0. */ + exit_arg = 0; + } + else if (code.get () != nullptr && PyLong_Check (code.get ())) + { + /* CODE == integer: exit status is aforementioned integer. */ + exit_arg = PyLong_AsLong (code.get ()); + } + else + { + if (code.get () == nullptr) + gdbpy_print_stack (); + + /* Otherwise: exit status is 1, print code to stderr. */ + if (msg != nullptr) + gdb_printf (gdb_stderr, "%s\n", msg.get ()); + exit_arg = 1; + } + + quit_force (&exit_arg, 0); + } else if (! fetched_error.type_matches (gdbpy_gdberror_exc) || msg == NULL || *msg == '\0') { diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c index dada8bf..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); } @@ -233,7 +230,8 @@ valpy_init (PyObject *self, PyObject *args, PyObject *kwds) each. */ void gdbpy_preserve_values (const struct extension_language_defn *extlang, - struct objfile *objfile, htab_t copied_types) + struct objfile *objfile, + copied_types_hash_t &copied_types) { value_object *iter; @@ -257,7 +255,7 @@ valpy_dereference (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -300,7 +298,7 @@ valpy_referenced_value (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -323,7 +321,7 @@ valpy_reference_value (PyObject *self, PyObject *args, enum type_code refcode) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -369,7 +367,7 @@ valpy_to_array (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -393,7 +391,7 @@ valpy_const_value (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -437,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. */ @@ -453,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 { @@ -492,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) { - GDB_PY_HANDLE_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]) -> @@ -520,14 +496,7 @@ valpy_get_dynamic_type (PyObject *self, void *closure) If LENGTH is provided then the length parameter is set to LENGTH. Otherwise if the value is an array of known length then the array's length is used. Otherwise the length will be set to -1 (meaning first null of - appropriate with). - - Note: In order to not break any existing uses this allows creating - lazy strings from anything. PR 20769. E.g., - gdb.parse_and_eval("my_int_variable").lazy_string(). - "It's easier to relax restrictions than it is to impose them after the - fact." So we should be flagging any unintended uses as errors, but it's - perhaps too late for that. */ + appropriate with). */ static PyObject * valpy_lazy_string (PyObject *self, PyObject *args, PyObject *kw) @@ -595,9 +564,7 @@ valpy_lazy_string (PyObject *self, PyObject *args, PyObject *kw) addr = value_as_address (value); break; default: - /* Should flag an error here. PR 20769. */ - addr = value->address (); - break; + error (_("Cannot make lazy string from this object")); } str_obj = gdbpy_create_lazy_string_object (addr, length, user_encoding, @@ -605,7 +572,7 @@ valpy_lazy_string (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return str_obj; @@ -640,7 +607,7 @@ valpy_string (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } encoding = (user_encoding && *user_encoding) ? user_encoding : la_encoding; @@ -815,7 +782,9 @@ valpy_format_string (PyObject *self, PyObject *args, PyObject *kw) } } - string_file stb (PyObject_IsTrue (styling_obj)); + /* We force styling_obj to be a 'bool' when we parse the args above. */ + gdb_assert (PyBool_Check (styling_obj)); + string_file stb (styling_obj == Py_True); try { @@ -824,7 +793,7 @@ valpy_format_string (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_Decode (stb.c_str (), stb.size (), host_charset (), NULL); @@ -869,7 +838,7 @@ valpy_do_cast (PyObject *self, PyObject *args, enum exp_opcode op) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -921,8 +890,7 @@ valpy_assign_core (value_object *self, struct value *new_value) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return false; + return gdbpy_handle_gdb_exception (false, except); } return true; @@ -997,7 +965,7 @@ value_has_field (struct value *v, PyObject *field) } catch (const gdb_exception &except) { - GDB_PY_SET_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (-1, except); } return has_field; @@ -1128,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; @@ -1178,9 +1145,9 @@ valpy_getitem (PyObject *self, PyObject *key) if (res_val) result = value_to_value_object (res_val); } - catch (gdb_exception &ex) + catch (const gdb_exception &ex) { - GDB_PY_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (nullptr, ex); } return result; @@ -1211,7 +1178,7 @@ valpy_call (PyObject *self, PyObject *args, PyObject *keywords) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (ftype->code () != TYPE_CODE_FUNC && ftype->code () != TYPE_CODE_METHOD @@ -1258,7 +1225,8 @@ valpy_call (PyObject *self, PyObject *args, PyObject *keywords) if (ftype->code () == TYPE_CODE_INTERNAL_FUNCTION) return_value = call_internal_function (gdbpy_enter::get_gdbarch (), current_language, - function, args_count, vargs); + function, args_count, vargs, + EVAL_NORMAL); else return_value = call_function_by_hand (function, NULL, @@ -1267,7 +1235,7 @@ valpy_call (PyObject *self, PyObject *args, PyObject *keywords) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -1292,7 +1260,7 @@ valpy_str (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyUnicode_Decode (stb.c_str (), stb.size (), host_charset (), NULL); @@ -1311,7 +1279,7 @@ valpy_get_is_optimized_out (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (opt) @@ -1333,7 +1301,7 @@ valpy_get_is_lazy (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (opt) @@ -1363,7 +1331,7 @@ valpy_get_bytes (PyObject *self, void *closure) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } value_obj->content_bytes @@ -1407,7 +1375,7 @@ valpy_fetch_lazy (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -1578,7 +1546,7 @@ valpy_binop (enum valpy_opcode opcode, PyObject *self, PyObject *other) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -1646,7 +1614,7 @@ valpy_negative (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -1673,7 +1641,7 @@ valpy_absolute (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (isabs) @@ -1703,12 +1671,12 @@ valpy_nonzero (PyObject *self) /* All other values are True. */ nonzero = 1; } - catch (gdb_exception &ex) + catch (const gdb_exception &ex) { /* This is not documented in the Python documentation, but if this function fails, return -1 as slot_nb_nonzero does (the default Python nonzero function). */ - GDB_PY_SET_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (-1, ex); } return nonzero; @@ -1728,7 +1696,7 @@ valpy_invert (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -1855,7 +1823,7 @@ valpy_richcompare (PyObject *self, PyObject *other, int op) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } /* In this case, the Python exception has already been set. */ @@ -1894,7 +1862,7 @@ valpy_long (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (type->is_unsigned ()) @@ -1929,7 +1897,7 @@ valpy_float (PyObject *self) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return PyFloat_FromDouble (d); @@ -1943,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); } @@ -1988,9 +1955,8 @@ convert_value_from_python (PyObject *obj) { if (PyBool_Check (obj)) { - cmp = PyObject_IsTrue (obj); - if (cmp >= 0) - value = value_from_longest (builtin_type_pybool, cmp); + cmp = obj == Py_True ? 1 : 0; + value = value_from_longest (builtin_type_pybool, cmp); } else if (PyLong_Check (obj)) { @@ -2055,8 +2021,7 @@ convert_value_from_python (PyObject *obj) } catch (const gdb_exception &except) { - gdbpy_convert_exception (except); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, except); } return value; @@ -2080,7 +2045,7 @@ gdbpy_history (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -2107,7 +2072,7 @@ gdbpy_add_history (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return nullptr; @@ -2152,7 +2117,7 @@ gdbpy_convenience_variable (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } if (result == nullptr && !found) @@ -2198,7 +2163,7 @@ gdbpy_set_convenience_variable (PyObject *self, PyObject *args) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -2215,11 +2180,7 @@ gdbpy_is_value_object (PyObject *obj) static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_values (void) { - if (PyType_Ready (&value_object_type) < 0) - return -1; - - return gdb_pymodule_addobject (gdb_module, "Value", - (PyObject *) &value_object_type); + return gdbpy_type_ready (&value_object_type); } GDBPY_INITIALIZE_FILE (gdbpy_initialize_values); 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 f25cd3b..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. @@ -17,11 +17,12 @@ 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_PYTHON_INTERNAL_H -#define PYTHON_PYTHON_INTERNAL_H +#ifndef GDB_PYTHON_PYTHON_INTERNAL_H +#define GDB_PYTHON_PYTHON_INTERNAL_H #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> @@ -464,6 +456,9 @@ extern enum ext_lang_rc gdbpy_apply_val_pretty_printer struct ui_file *stream, int recurse, const struct value_print_options *options, const struct language_defn *language); +extern void gdbpy_load_ptwrite_filter + (const struct extension_language_defn *extlang, + struct btrace_thread_info *btinfo); extern enum ext_lang_bt_status gdbpy_apply_frame_filter (const struct extension_language_defn *, const frame_info_ptr &frame, frame_filter_flags flags, @@ -471,7 +466,7 @@ extern enum ext_lang_bt_status gdbpy_apply_frame_filter struct ui_out *out, int frame_low, int frame_high); extern void gdbpy_preserve_values (const struct extension_language_defn *, struct objfile *objfile, - htab_t copied_types); + copied_types_hash_t &copied_types); extern enum ext_lang_bp_stop gdbpy_breakpoint_cond_says_stop (const struct extension_language_defn *, struct breakpoint *); extern int gdbpy_breakpoint_has_cond (const struct extension_language_defn *, @@ -515,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); @@ -929,26 +925,10 @@ private: PyGILState_STATE m_state; }; -/* Use this in a 'catch' block to convert the exception to a Python - exception and return nullptr. */ -#define GDB_PY_HANDLE_EXCEPTION(Exception) \ - do { \ - gdbpy_convert_exception (Exception); \ - return nullptr; \ - } while (0) - -/* Use this in a 'catch' block to convert the exception to a Python - exception and return -1. */ -#define GDB_PY_SET_HANDLE_EXCEPTION(Exception) \ - do { \ - gdbpy_convert_exception (Exception); \ - return -1; \ - } while (0) - int gdbpy_print_python_errors_p (void); void gdbpy_print_stack (void); void gdbpy_print_stack_or_quit (); -void gdbpy_handle_exception () ATTRIBUTE_NORETURN; +[[noreturn]] void gdbpy_handle_exception (); /* A wrapper around calling 'error'. Prefixes the error message with an 'Error occurred in Python' string. Use this in C++ code if we spot @@ -958,8 +938,7 @@ void gdbpy_handle_exception () ATTRIBUTE_NORETURN; This always calls error, and never returns. */ -void gdbpy_error (const char *fmt, ...) - ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF (1, 2); +[[noreturn]] void gdbpy_error (const char *fmt, ...) ATTRIBUTE_PRINTF (1, 2); gdbpy_ref<> python_string_to_unicode (PyObject *obj); gdb::unique_xmalloc_ptr<char> unicode_to_target_string (PyObject *unicode_str); @@ -1011,6 +990,18 @@ extern PyObject *gdbpy_gdberror_exc; extern void gdbpy_convert_exception (const struct gdb_exception &) CPYCHECKER_SETS_EXCEPTION; + /* Use this in a 'catch' block to convert the exception E to a Python + exception and return value VAL to signal that an exception occurred. + Typically at the use site, that value will be returned immediately. */ + +template<typename T> +[[nodiscard]] T +gdbpy_handle_gdb_exception (T val, const gdb_exception &e) +{ + gdbpy_convert_exception (e); + return val; +} + int get_addr_from_python (PyObject *obj, CORE_ADDR *addr) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; @@ -1117,4 +1108,228 @@ extern std::optional<int> gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR address, disassemble_info *info); -#endif /* PYTHON_PYTHON_INTERNAL_H */ +/* A wrapper for PyType_Ready that also automatically registers the + type in the appropriate module. Returns 0 on success, -1 on error. + If MOD is supplied, then the type is added to that module. If MOD + is not supplied, the type name (tp_name field) must be of the form + "gdb.Mumble", and the type will be added to the gdb module. */ + +static inline int +gdbpy_type_ready (PyTypeObject *type, PyObject *mod = nullptr) +{ + if (PyType_Ready (type) < 0) + return -1; + if (mod == nullptr) + { + gdb_assert (startswith (type->tp_name, "gdb.")); + mod = gdb_module; + } + const char *dot = strrchr (type->tp_name, '.'); + gdb_assert (dot != nullptr); + return gdb_pymodule_addobject (mod, dot + 1, (PyObject *) type); +} + +/* Poison PyType_Ready. Only gdbpy_type_ready should be used, to + avoid forgetting to register the type. See PR python/32163. */ +#undef PyType_Ready +#ifdef __GNUC__ +# pragma GCC poison PyType_Ready +#else +# 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 e9092b4..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. @@ -35,6 +35,8 @@ #include "location.h" #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" @@ -76,6 +78,7 @@ static const char *gdbpy_should_print_stack = python_excp_message; #include "interps.h" #include "event-top.h" #include "py-event.h" +#include "py-color.h" /* True if Python has been successfully initialized, false otherwise. */ @@ -125,11 +128,15 @@ 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_debuginfo_result gdbpy_handle_missing_debuginfo +static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, struct objfile *objfile); +static ext_lang_missing_file_result gdbpy_find_objfile_from_buildid + (const struct extension_language_defn *extlang, program_space *pspace, + const struct bfd_build_id *build_id, const char *missing_filename); /* The interface between gdb proper and loading of python scripts. */ @@ -159,6 +166,8 @@ static const struct extension_language_ops python_extension_ops = gdbpy_apply_frame_filter, + gdbpy_load_ptwrite_filter, + gdbpy_preserve_values, gdbpy_breakpoint_has_cond, @@ -177,7 +186,8 @@ static const struct extension_language_ops python_extension_ops = gdbpy_print_insn, - gdbpy_handle_missing_debuginfo + gdbpy_handle_missing_debuginfo, + gdbpy_find_objfile_from_buildid }; #endif /* HAVE_PYTHON */ @@ -504,6 +514,12 @@ gdbpy_parameter_value (const setting &var) return host_string_to_python_string (str).release (); } + case var_color: + { + const ui_file_style::color &color = var.get<ui_file_style::color> (); + return create_color_object (color).release (); + } + case var_boolean: { if (var.get<bool> ()) @@ -591,7 +607,7 @@ gdbpy_parameter (PyObject *self, PyObject *args) } catch (const gdb_exception &ex) { - GDB_PY_HANDLE_EXCEPTION (ex); + return gdbpy_handle_gdb_exception (nullptr, ex); } if (cmd == CMD_LIST_AMBIGUOUS) @@ -646,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; @@ -672,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 (); @@ -731,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); } @@ -754,11 +796,12 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw) convert the exception and continue back in Python, we should re-enable stdin here. */ async_enable_stdin (); - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } 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; } @@ -963,15 +1006,14 @@ gdbpy_decode_line (PyObject *self, PyObject *args) else { set_default_source_symtab_and_line (); - def_sal = get_current_source_symtab_and_line (); + def_sal = get_current_source_symtab_and_line (current_program_space); sals = def_sal; } } catch (const gdb_exception &ex) { /* We know this will always throw. */ - gdbpy_convert_exception (ex); - return NULL; + return gdbpy_handle_gdb_exception (nullptr, ex); } if (!sals.empty ()) @@ -1052,7 +1094,7 @@ gdbpy_parse_and_eval (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } return result; @@ -1254,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 {}; @@ -1288,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 @@ -1308,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) { @@ -1537,7 +1588,7 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw) } catch (const gdb_exception &except) { - GDB_PY_HANDLE_EXCEPTION (except); + return gdbpy_handle_gdb_exception (nullptr, except); } Py_RETURN_NONE; @@ -1561,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; @@ -1754,10 +1842,10 @@ gdbpy_get_current_objfile (PyObject *unused1, PyObject *unused2) /* Implement the 'handle_missing_debuginfo' hook for Python. GDB has failed to find any debug information for OBJFILE. The extension has a chance to record this, or even install the required debug information. - See the description of ext_lang_missing_debuginfo_result in - extension-priv.h for details of the return value. */ + See the description of ext_lang_missing_file_result in extension-priv.h + for details of the return value. */ -static ext_lang_missing_debuginfo_result +static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, struct objfile *objfile) { @@ -1804,8 +1892,10 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, if (PyBool_Check (pyo_execute_ret.get ())) { - bool try_again = PyObject_IsTrue (pyo_execute_ret.get ()); - return ext_lang_missing_debuginfo_result (try_again); + /* We know the value is a bool, so it must be either Py_True or + Py_False. Anything else would not get past the above check. */ + bool try_again = pyo_execute_ret.get () == Py_True; + return ext_lang_missing_file_result (try_again); } if (!gdbpy_is_string (pyo_execute_ret.get ())) @@ -1825,7 +1915,108 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, return {}; } - return ext_lang_missing_debuginfo_result (std::string (filename.get ())); + return ext_lang_missing_file_result (std::string (filename.get ())); +} + +/* Implement the find_objfile_from_buildid hook for Python. PSPACE is the + program space in which GDB is trying to find an objfile, BUILD_ID is the + build-id for the missing objfile, and EXPECTED_FILENAME is a non-NULL + string which can be used (if needed) in messages to the user, and + represents the file GDB is looking for. */ + +static ext_lang_missing_file_result +gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang, + program_space *pspace, + const struct bfd_build_id *build_id, + const char *missing_filename) +{ + gdb_assert (pspace != nullptr); + gdb_assert (build_id != nullptr); + gdb_assert (missing_filename != nullptr); + + /* Early exit if Python is not initialised. */ + if (!gdb_python_initialized || gdb_python_module == nullptr) + return {}; + + gdbpy_enter enter_py; + + /* Convert BUILD_ID into a Python object. */ + std::string hex_form = bin2hex (build_id->data, build_id->size); + gdbpy_ref<> pyo_buildid = host_string_to_python_string (hex_form.c_str ()); + if (pyo_buildid == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Convert MISSING_FILENAME to a Python object. */ + gdbpy_ref<> pyo_filename = host_string_to_python_string (missing_filename); + if (pyo_filename == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Convert PSPACE to a Python object. */ + gdbpy_ref<> pyo_pspace = pspace_to_pspace_object (pspace); + if (pyo_pspace == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Lookup the helper function within the GDB module. */ + gdbpy_ref<> pyo_handler + (PyObject_GetAttrString (gdb_python_module, "_handle_missing_objfile")); + if (pyo_handler == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Call the function, passing in the Python objfile object. */ + gdbpy_ref<> pyo_execute_ret + (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_pspace.get (), + pyo_buildid.get (), pyo_filename.get (), + nullptr)); + if (pyo_execute_ret == nullptr) + { + /* If the handler is cancelled due to a Ctrl-C, then propagate + the Ctrl-C as a GDB exception instead of swallowing it. */ + gdbpy_print_stack_or_quit (); + return {}; + } + + /* Parse the result, and convert it back to the C++ object. */ + if (pyo_execute_ret == Py_None) + return {}; + + if (PyBool_Check (pyo_execute_ret.get ())) + { + /* We know the value is a bool, so it must be either Py_True or + Py_False. Anything else would not get past the above check. */ + bool try_again = pyo_execute_ret.get () == Py_True; + return ext_lang_missing_file_result (try_again); + } + + if (!gdbpy_is_string (pyo_execute_ret.get ())) + { + PyErr_SetString (PyExc_ValueError, + "return value from _find_objfile_by_buildid should " + "be None, a bool, or a str"); + gdbpy_print_stack (); + return {}; + } + + gdb::unique_xmalloc_ptr<char> filename + = python_string_to_host_string (pyo_execute_ret.get ()); + if (filename == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + return ext_lang_missing_file_result (std::string (filename.get ())); } /* Compute the list of active python type printers and store them in @@ -1982,6 +2173,17 @@ python_command (const char *arg, int from_tty) #endif /* HAVE_PYTHON */ +/* Stand-in for Py_IsInitialized (). To be used because after a python fatal + error, no calls into Python are allowed. */ + +static bool py_isinitialized = false; + +/* Variables to hold the effective values of "python ignore-environment" and + "python dont-write-bytecode" at Python initialization. */ + +static bool python_ignore_environment_at_python_initialization; +static bool python_dont_write_bytecode_at_python_initialization; + /* When this is turned on before Python is initialised then Python will ignore any environment variables related to Python. This is equivalent to passing `-E' to the python program. */ @@ -2006,21 +2208,32 @@ static void set_python_ignore_environment (const char *args, int from_tty, struct cmd_list_element *c) { -#ifdef HAVE_PYTHON - /* Py_IgnoreEnvironmentFlag is deprecated in Python 3.12. Disable - its usage in Python 3.10 and above since the PyConfig mechanism - is now (also) used in 3.10 and higher. See do_start_initialization() - in this file. */ -#if PY_VERSION_HEX < 0x030a0000 - Py_IgnoreEnvironmentFlag = python_ignore_environment ? 1 : 0; -#endif -#endif + if (py_isinitialized) + { + python_ignore_environment + = python_ignore_environment_at_python_initialization; + + warning (_("Setting python ignore-environment after Python" + " initialization has no effect, try setting this during" + " early initialization")); + } } /* When this is turned on before Python is initialised then Python will not write `.pyc' files on import of a module. */ static enum auto_boolean python_dont_write_bytecode = AUTO_BOOLEAN_AUTO; + +/* Return true if environment variable PYTHONDONTWRITEBYTECODE is set to a + non-empty string. */ + +static bool +env_python_dont_write_bytecode () +{ + const char *envvar = getenv ("PYTHONDONTWRITEBYTECODE"); + return envvar != nullptr && envvar[0] != '\0'; +} + /* Implement 'show python dont-write-bytecode'. */ static void @@ -2030,8 +2243,10 @@ show_python_dont_write_bytecode (struct ui_file *file, int from_tty, if (python_dont_write_bytecode == AUTO_BOOLEAN_AUTO) { const char *auto_string - = (python_ignore_environment - || getenv ("PYTHONDONTWRITEBYTECODE") == nullptr) ? "off" : "on"; + = ((python_ignore_environment + || !env_python_dont_write_bytecode ()) + ? "off" + : "on"); gdb_printf (file, _("Python's dont-write-bytecode setting is %s (currently %s).\n"), @@ -2057,10 +2272,7 @@ python_write_bytecode () if (python_ignore_environment) wbc = 1; else - { - const char *pdwbc = getenv ("PYTHONDONTWRITEBYTECODE"); - wbc = (pdwbc == nullptr || pdwbc[0] == '\0') ? 1 : 0; - } + wbc = env_python_dont_write_bytecode () ? 0 : 1; } else wbc = python_dont_write_bytecode == AUTO_BOOLEAN_TRUE ? 0 : 1; @@ -2078,15 +2290,18 @@ static void set_python_dont_write_bytecode (const char *args, int from_tty, struct cmd_list_element *c) { -#ifdef HAVE_PYTHON - /* Py_DontWriteBytecodeFlag is deprecated in Python 3.12. Disable - its usage in Python 3.10 and above since the PyConfig mechanism - is now (also) used in 3.10 and higher. See do_start_initialization() - in this file. */ -#if PY_VERSION_HEX < 0x030a0000 - Py_DontWriteBytecodeFlag = !python_write_bytecode (); -#endif -#endif /* HAVE_PYTHON */ + if (py_isinitialized) + { + python_dont_write_bytecode + = (python_dont_write_bytecode_at_python_initialization + ? AUTO_BOOLEAN_TRUE + : AUTO_BOOLEAN_FALSE); + + warning (_("Setting python dont-write-bytecode after Python" + " initialization has no effect, try setting this during" + " early initialization, or try setting" + " sys.dont_write_bytecode")); + } } @@ -2106,6 +2321,9 @@ static struct cmd_list_element *user_show_python_list; static void finalize_python (const struct extension_language_defn *ignore) { + if (!gdb_python_initialized) + return; + struct active_ext_lang_state *previous_active; /* We don't use ensure_python_env here because if we ever ran the @@ -2188,20 +2406,71 @@ gdbpy_gdb_exiting (int exit_code) gdbpy_print_stack (); } +#if PY_VERSION_HEX < 0x030a0000 +/* Signal handler to convert a SIGABRT into an exception. */ + +static void +catch_python_fatal (int signum) +{ + signal (SIGABRT, catch_python_fatal); + + throw_exception_sjlj (gdb_exception {RETURN_ERROR, GENERIC_ERROR}); +} + +/* Call Py_Initialize (), and return true if successful. */ + static bool -do_start_initialization () +py_initialize_catch_abort () { - /* Define all internal modules. These are all imported (and thus - created) during initialization. */ - struct _inittab mods[] = - { - { "_gdb", init__gdb_module }, - { "_gdbevents", gdbpy_events_mod_func }, - { nullptr, nullptr } - }; + auto prev_handler = signal (SIGABRT, catch_python_fatal); + SCOPE_EXIT { signal (SIGABRT, prev_handler); }; - if (PyImport_ExtendInittab (mods) < 0) - return false; + TRY_SJLJ + { + Py_Initialize (); + py_isinitialized = true; + } + CATCH_SJLJ (e, RETURN_MASK_ERROR) + { + } + END_CATCH_SJLJ; + + return py_isinitialized; +} +#endif + +/* Initialize python, either by calling Py_Initialize or + Py_InitializeFromConfig, and return true if successful. */ + +static bool +py_initialize () +{ + /* Sample values at Python initialization. */ + python_dont_write_bytecode_at_python_initialization + = !python_write_bytecode (); + python_ignore_environment_at_python_initialization + = python_ignore_environment; + + /* Don't show "python dont-write-bytecode auto" after Python + initialization. */ + python_dont_write_bytecode + = (python_dont_write_bytecode_at_python_initialization + ? AUTO_BOOLEAN_TRUE + : AUTO_BOOLEAN_FALSE); + +#if PY_VERSION_HEX < 0x030a0000 + /* Python documentation indicates that the memory given + to Py_SetProgramName cannot be freed. However, it seems that + at least Python 3.7.4 Py_SetProgramName takes a copy of the + given program_name. Making progname_copy static and not release + the memory avoids a leak report for Python versions that duplicate + program_name, and respect the requirement of Py_SetProgramName + for Python versions that do not duplicate program_name. */ + static wchar_t *progname_copy = nullptr; +#else + wchar_t *progname_copy = nullptr; + SCOPE_EXIT { XDELETEVEC (progname_copy); }; +#endif #ifdef WITH_PYTHON_PATH /* Work around problem where python gets confused about where it is, @@ -2211,28 +2480,24 @@ do_start_initialization () /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)); - /* Python documentation indicates that the memory given - to Py_SetProgramName cannot be freed. However, it seems that - at least Python 3.7.4 Py_SetProgramName takes a copy of the - given program_name. Making progname_copy static and not release - the memory avoids a leak report for Python versions that duplicate - program_name, and respect the requirement of Py_SetProgramName - for Python versions that do not duplicate program_name. */ - static wchar_t *progname_copy; - std::string oldloc = setlocale (LC_ALL, NULL); - setlocale (LC_ALL, ""); - size_t progsize = strlen (progname.get ()); - progname_copy = XNEWVEC (wchar_t, progsize + 1); - size_t count = mbstowcs (progname_copy, progname.get (), progsize + 1); - if (count == (size_t) -1) - { - fprintf (stderr, "Could not convert python path to string\n"); - return false; - } - setlocale (LC_ALL, oldloc.c_str ()); + { + std::string oldloc = setlocale (LC_ALL, NULL); + SCOPE_EXIT { setlocale (LC_ALL, oldloc.c_str ()); }; + + setlocale (LC_ALL, ""); + size_t progsize = strlen (progname.get ()); + progname_copy = XNEWVEC (wchar_t, progsize + 1); + size_t count = mbstowcs (progname_copy, progname.get (), progsize + 1); + if (count == (size_t) -1) + { + fprintf (stderr, "Could not convert python path to string\n"); + return false; + } + } +#endif /* Py_SetProgramName was deprecated in Python 3.11. Use PyConfig mechanisms for Python 3.10 and newer. */ @@ -2240,19 +2505,28 @@ do_start_initialization () /* Note that Py_SetProgramName expects the string it is passed to remain alive for the duration of the program's execution, so it is not freed after this call. */ - Py_SetProgramName (progname_copy); - Py_Initialize (); + if (progname_copy != nullptr) + Py_SetProgramName (progname_copy); + Py_DontWriteBytecodeFlag + = python_dont_write_bytecode_at_python_initialization; + Py_IgnoreEnvironmentFlag + = python_ignore_environment_at_python_initialization ? 1 : 0; + return py_initialize_catch_abort (); #else PyConfig config; PyConfig_InitPythonConfig (&config); - PyStatus status = PyConfig_SetString (&config, &config.program_name, - progname_copy); - if (PyStatus_Exception (status)) - goto init_done; + PyStatus status; + if (progname_copy != nullptr) + { + status = PyConfig_SetString (&config, &config.program_name, + progname_copy); + if (PyStatus_Exception (status)) + goto init_done; + } - config.write_bytecode = python_write_bytecode (); - config.use_environment = !python_ignore_environment; + config.write_bytecode = !python_dont_write_bytecode_at_python_initialization; + config.use_environment = !python_ignore_environment_at_python_initialization; status = PyConfig_Read (&config); if (PyStatus_Exception (status)) @@ -2263,11 +2537,37 @@ do_start_initialization () init_done: PyConfig_Clear (&config); if (PyStatus_Exception (status)) - return false; -#endif -#else - Py_Initialize (); + { + if (PyStatus_IsError (status)) + gdb_printf (_("Python initialization failed: %s\n"), status.err_msg); + else + gdb_printf (_("Python initialization failed with exit status: %d\n"), + status.exitcode); + return false; + } + + py_isinitialized = true; + return true; #endif +} + +static bool +do_start_initialization () +{ + /* Define all internal modules. These are all imported (and thus + created) during initialization. */ + struct _inittab mods[] = + { + { "_gdb", init__gdb_module }, + { "_gdbevents", gdbpy_events_mod_func }, + { nullptr, nullptr } + }; + + if (PyImport_ExtendInittab (mods) < 0) + return false; + + if (!py_initialize ()) + return false; #if PY_VERSION_HEX < 0x03090000 /* PyEval_InitThreads became deprecated in Python 3.9 and will @@ -2315,7 +2615,7 @@ init_done: return false; #define GDB_PY_DEFINE_EVENT_TYPE(name, py_name, doc, base) \ - if (gdbpy_initialize_event_generic (&name##_event_object_type, py_name) < 0) \ + if (gdbpy_type_ready (&name##_event_object_type) < 0) \ return false; #include "py-event-types.def" #undef GDB_PY_DEFINE_EVENT_TYPE @@ -2409,7 +2709,7 @@ test_python () #undef CHECK_OUTPUT -} // namespace selftests +} /* namespace selftests */ #endif /* GDB_SELF_TEST */ #endif /* HAVE_PYTHON */ @@ -2417,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, @@ -2601,6 +2899,39 @@ do_initialize (const struct extension_language_defn *extlang) return gdb_pymodule_addobject (m, "gdb", gdb_python_module) >= 0; } +/* Emit warnings in case python initialization has failed. */ + +static void +python_initialization_failed_warnings () +{ + const char *pythonhome = nullptr; + const char *pythonpath = nullptr; + + if (!python_ignore_environment) + { + pythonhome = getenv ("PYTHONHOME"); + pythonpath = getenv ("PYTHONPATH"); + } + + bool have_pythonhome + = pythonhome != nullptr && pythonhome[0] != '\0'; + bool have_pythonpath + = pythonpath != nullptr && pythonpath[0] != '\0'; + + if (have_pythonhome) + warning (_("Python failed to initialize with PYTHONHOME set. Maybe" + " because it is set incorrectly? Maybe because it points to" + " incompatible standard libraries? Consider changing or" + " unsetting it, or ignoring it using \"set python" + " ignore-environment on\" at early initialization.")); + + if (have_pythonpath) + warning (_("Python failed to initialize with PYTHONPATH set. Maybe because" + " it points to incompatible modules? Consider changing or" + " unsetting it, or ignoring it using \"set python" + " ignore-environment on\" at early initialization.")); +} + /* Perform Python initialization. This will be called after GDB has performed all of its own initialization. This is the extension_language_ops.initialize "method". */ @@ -2608,8 +2939,23 @@ do_initialize (const struct extension_language_defn *extlang) static void gdbpy_initialize (const struct extension_language_defn *extlang) { - if (!do_start_initialization () && PyErr_Occurred ()) - gdbpy_print_stack (); + if (!do_start_initialization ()) + { + if (py_isinitialized) + { + if (PyErr_Occurred ()) + gdbpy_print_stack (); + + /* We got no use for the Python interpreter anymore. Finalize it + ASAP. */ + Py_Finalize (); + } + else + python_initialization_failed_warnings (); + + /* Continue with python disabled. */ + return; + } gdbpy_enter enter_py; @@ -2810,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 e627948..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. @@ -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_PYTHON_H -#define PYTHON_PYTHON_H +#ifndef GDB_PYTHON_PYTHON_H +#define GDB_PYTHON_PYTHON_H #include "extension.h" @@ -34,4 +34,4 @@ extern cmd_list_element *python_cmd_element; at other times. */ extern struct objfile *gdbpy_current_objfile; -#endif /* PYTHON_PYTHON_H */ +#endif /* GDB_PYTHON_PYTHON_H */ |