aboutsummaryrefslogtreecommitdiff
path: root/gdb/python/lib
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/python/lib')
-rw-r--r--gdb/python/lib/gdb/FrameDecorator.py7
-rw-r--r--gdb/python/lib/gdb/FrameIterator.py2
-rw-r--r--gdb/python/lib/gdb/__init__.py265
-rw-r--r--gdb/python/lib/gdb/command/__init__.py2
-rw-r--r--gdb/python/lib/gdb/command/explore.py2
-rw-r--r--gdb/python/lib/gdb/command/frame_filters.py2
-rw-r--r--gdb/python/lib/gdb/command/missing_files.py (renamed from gdb/python/lib/gdb/command/missing_debug.py)137
-rw-r--r--gdb/python/lib/gdb/command/pretty_printers.py2
-rw-r--r--gdb/python/lib/gdb/command/prompt.py2
-rw-r--r--gdb/python/lib/gdb/command/type_printers.py2
-rw-r--r--gdb/python/lib/gdb/command/unwinders.py2
-rw-r--r--gdb/python/lib/gdb/command/xmethods.py4
-rw-r--r--gdb/python/lib/gdb/dap/__init__.py4
-rw-r--r--gdb/python/lib/gdb/dap/breakpoint.py30
-rw-r--r--gdb/python/lib/gdb/dap/bt.py9
-rw-r--r--gdb/python/lib/gdb/dap/completions.py63
-rw-r--r--gdb/python/lib/gdb/dap/disassemble.py22
-rw-r--r--gdb/python/lib/gdb/dap/evaluate.py8
-rw-r--r--gdb/python/lib/gdb/dap/events.py9
-rw-r--r--gdb/python/lib/gdb/dap/frames.py28
-rw-r--r--gdb/python/lib/gdb/dap/globalvars.py17
-rw-r--r--gdb/python/lib/gdb/dap/io.py2
-rw-r--r--gdb/python/lib/gdb/dap/launch.py151
-rw-r--r--gdb/python/lib/gdb/dap/locations.py11
-rw-r--r--gdb/python/lib/gdb/dap/memory.py2
-rw-r--r--gdb/python/lib/gdb/dap/modules.py2
-rw-r--r--gdb/python/lib/gdb/dap/next.py17
-rw-r--r--gdb/python/lib/gdb/dap/pause.py2
-rw-r--r--gdb/python/lib/gdb/dap/scopes.py58
-rw-r--r--gdb/python/lib/gdb/dap/server.py413
-rw-r--r--gdb/python/lib/gdb/dap/sources.py11
-rw-r--r--gdb/python/lib/gdb/dap/startup.py2
-rw-r--r--gdb/python/lib/gdb/dap/state.py2
-rw-r--r--gdb/python/lib/gdb/dap/threads.py25
-rw-r--r--gdb/python/lib/gdb/dap/typecheck.py2
-rw-r--r--gdb/python/lib/gdb/dap/varref.py88
-rw-r--r--gdb/python/lib/gdb/disassembler.py6
-rw-r--r--gdb/python/lib/gdb/frames.py2
-rw-r--r--gdb/python/lib/gdb/function/__init__.py2
-rw-r--r--gdb/python/lib/gdb/function/as_string.py2
-rw-r--r--gdb/python/lib/gdb/function/caller_is.py2
-rw-r--r--gdb/python/lib/gdb/function/strfns.py2
-rw-r--r--gdb/python/lib/gdb/missing_debug.py163
-rw-r--r--gdb/python/lib/gdb/missing_files.py204
-rw-r--r--gdb/python/lib/gdb/missing_objfile.py67
-rw-r--r--gdb/python/lib/gdb/printer/__init__.py2
-rw-r--r--gdb/python/lib/gdb/printer/bound_registers.py39
-rw-r--r--gdb/python/lib/gdb/printing.py42
-rw-r--r--gdb/python/lib/gdb/prompt.py4
-rw-r--r--gdb/python/lib/gdb/ptwrite.py2
-rw-r--r--gdb/python/lib/gdb/styling.py13
-rw-r--r--gdb/python/lib/gdb/types.py2
-rw-r--r--gdb/python/lib/gdb/unwinder.py2
-rw-r--r--gdb/python/lib/gdb/xmethod.py13
54 files changed, 1341 insertions, 635 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 145aeb6..1c3cf8e 100644
--- a/gdb/python/lib/gdb/dap/__init__.py
+++ b/gdb/python/lib/gdb/dap/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 Free Software Foundation, Inc.
+# Copyright 2022-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ from . import startup
# server object. "F401" is the flake8 "imported but unused" code.
from . import breakpoint # noqa: F401
from . import bt # noqa: F401
+from . import completions # noqa: F401
from . import disassemble # noqa: F401
from . import evaluate # noqa: F401
from . import launch # noqa: F401
@@ -95,5 +96,4 @@ def pre_command_loop():
# These are handy for bug reports.
startup.exec_and_log("show version")
startup.exec_and_log("show configuration")
- global server
startup.start_dap(server.main_loop)
diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
index db8ac10..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,7 +21,7 @@ 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,
@@ -51,7 +51,6 @@ def suppress_new_breakpoint_event():
@in_gdb_thread
def _bp_modified(event):
- global _suppress_bp
if not _suppress_bp:
send_event(
"breakpoint",
@@ -64,7 +63,6 @@ def _bp_modified(event):
@in_gdb_thread
def _bp_created(event):
- global _suppress_bp
if not _suppress_bp:
send_event(
"breakpoint",
@@ -77,7 +75,6 @@ def _bp_created(event):
@in_gdb_thread
def _bp_deleted(event):
- global _suppress_bp
if not _suppress_bp:
send_event(
"breakpoint",
@@ -128,7 +125,7 @@ def _breakpoint_descriptor(bp):
result.update(
{
"source": make_source(filename),
- "line": line,
+ "line": export_line(line),
}
)
@@ -151,7 +148,6 @@ def _remove_entries(table, *names):
# the breakpoint.
@in_gdb_thread
def _set_breakpoints_callback(kind, specs, creator):
- global breakpoint_map
# Try to reuse existing breakpoints if possible.
if kind in breakpoint_map:
saved_map = breakpoint_map[kind]
@@ -208,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
@@ -218,11 +214,11 @@ class _PrintBreakpoint(gdb.Breakpoint):
def __init__(self, logMessage, **args):
super().__init__(**args)
# Split the message up for easier processing.
- self.message = re.split("{(.*?)}", logMessage)
+ self._message = re.split("{(.*?)}", logMessage)
def stop(self):
output = ""
- for idx, item in enumerate(self.message):
+ for idx, item in enumerate(self._message):
if idx % 2 == 0:
# Even indices are plain text.
output += item
@@ -281,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")
@@ -330,7 +326,7 @@ def _rewrite_fn_breakpoint(
}
-@request("setFunctionBreakpoints")
+@request("setFunctionBreakpoints", expect_stopped=False)
@capability("supportsFunctionBreakpoints")
def set_fn_breakpoint(*, breakpoints: Sequence, **args):
specs = [_rewrite_fn_breakpoint(**bp) for bp in breakpoints]
@@ -363,7 +359,7 @@ def _rewrite_insn_breakpoint(
}
-@request("setInstructionBreakpoints")
+@request("setInstructionBreakpoints", expect_stopped=False)
@capability("supportsInstructionBreakpoints")
def set_insn_breakpoints(
*, breakpoints: Sequence, offset: Optional[int] = None, **args
@@ -414,7 +410,7 @@ def _rewrite_exception_breakpoint(
}
-@request("setExceptionBreakpoints")
+@request("setExceptionBreakpoints", expect_stopped=False)
@capability("supportsExceptionFilterOptions")
@capability(
"exceptionBreakpointFilters",
diff --git a/gdb/python/lib/gdb/dap/bt.py b/gdb/python/lib/gdb/dap/bt.py
index 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 38bdc5c..9d64d28 100644
--- a/gdb/python/lib/gdb/dap/globalvars.py
+++ b/gdb/python/lib/gdb/dap/globalvars.py
@@ -1,4 +1,4 @@
-# Copyright 2024 Free Software Foundation, Inc.
+# Copyright 2024-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -37,8 +37,8 @@ gdb.events.cont.connect(clear)
class _Globals(BaseReference):
def __init__(self, filename, var_list):
super().__init__("Globals")
- self.filename = filename
- self.var_list = var_list
+ self._filename = filename
+ self._var_list = var_list
def to_object(self):
result = super().to_object()
@@ -46,8 +46,8 @@ class _Globals(BaseReference):
# How would we know?
result["expensive"] = False
result["namedVariables"] = self.child_count()
- if self.filename is not None:
- result["source"] = make_source(self.filename)
+ if self._filename is not None:
+ result["source"] = make_source(self._filename)
return result
def has_children(self):
@@ -56,11 +56,11 @@ class _Globals(BaseReference):
return True
def child_count(self):
- return len(self.var_list)
+ return len(self._var_list)
@in_gdb_thread
def fetch_one_child(self, idx):
- sym = self.var_list[idx]
+ sym = self._var_list[idx]
return (sym.name, sym.value())
@@ -78,7 +78,6 @@ def get_global_scope(frame):
except RuntimeError:
return None
- global _id_to_scope
block = block.static_block
if block in _id_to_scope:
return _id_to_scope[block]
@@ -86,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 65444bf..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
@@ -21,8 +21,42 @@ 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, in_gdb_thread
+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.
@@ -37,7 +71,7 @@ def file_command(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,
@@ -48,25 +82,51 @@ def launch(
stopOnEntry: bool = False,
**extra,
):
- if cwd is not None:
- exec_and_log("cd " + cwd)
- if program is not None:
- file_command(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("starti" if stopOnEntry else "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,
@@ -74,21 +134,38 @@ def attach(
target: Optional[str] = None,
**args,
):
- 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)
+ # 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 967322f..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,7 +16,7 @@
# This is deprecated in 3.9, but required in older versions.
from typing import Optional
-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
@@ -28,15 +28,18 @@ from .startup import exec_mi_and_log
# 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 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 fb90f64..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,46 +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 wraps the 'finish' value. Note that this
# object is only created if such a value actually exists.
class _FinishScopeReference(_ScopeReference):
- def __init__(self, frame):
- super().__init__("Return", "returnValue", frame, ())
+ def __init__(self, frameId):
+ super().__init__("Return", "returnValue", frameId, ())
def child_count(self):
return 1
def fetch_one_child(self, idx):
assert idx == 0
- global _last_return_value
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:
@@ -153,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", frame, locs))
- scopes.append(_RegisterReference("Registers", frame))
+ scopes.append(_ScopeReference("Locals", "locals", frameId, locs))
+ scopes.append(_RegisterReference("Registers", frameId))
if has_return_value:
- scopes.append(_FinishScopeReference(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 a9f4ea6..efcd799 100644
--- a/gdb/python/lib/gdb/dap/sources.py
+++ b/gdb/python/lib/gdb/dap/sources.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 Free Software Foundation, Inc.
+# Copyright 2023-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -37,7 +37,6 @@ def make_source(fullname, filename=None):
FILENAME is the base name; if None (the default), then it is
computed from FULLNAME.
"""
- global _source_map
if fullname in _source_map:
result = _source_map[fullname]
else:
@@ -53,7 +52,6 @@ def make_source(fullname, filename=None):
global _next_source
result["sourceReference"] = _next_source
- global _id_map
_id_map[_next_source] = result
_next_source += 1
@@ -66,12 +64,11 @@ def decode_source(source):
"""Decode a Source object.
Finds and returns the filename of a given Source object."""
- if "path" in source:
- return source["path"]
- if "sourceReference" not in source:
+ if "sourceReference" not in source or source["sourceReference"] <= 0:
+ if "path" in source:
+ return source["path"]
raise DAPException("either 'path' or 'sourceReference' must appear in Source")
ref = source["sourceReference"]
- global _id_map
if ref not in _id_map:
raise DAPException("no sourceReference " + str(ref))
return _id_map[ref]["path"]
diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py
index a3f048b..ab3e8fd 100644
--- a/gdb/python/lib/gdb/dap/startup.py
+++ b/gdb/python/lib/gdb/dap/startup.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 Free Software Foundation, Inc.
+# Copyright 2022-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gdb/python/lib/gdb/dap/state.py b/gdb/python/lib/gdb/dap/state.py
index 57ae355..5fdfbb2 100644
--- a/gdb/python/lib/gdb/dap/state.py
+++ b/gdb/python/lib/gdb/dap/state.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 Free Software Foundation, Inc.
+# Copyright 2022-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gdb/python/lib/gdb/dap/threads.py b/gdb/python/lib/gdb/dap/threads.py
index e65495b..89046a8 100644
--- a/gdb/python/lib/gdb/dap/threads.py
+++ b/gdb/python/lib/gdb/dap/threads.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 Free Software Foundation, Inc.
+# Copyright 2022-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,27 +16,32 @@
import gdb
from .server import request
+from .startup import in_gdb_thread
+@in_gdb_thread
def _thread_name(thr):
if thr.name is not None:
return thr.name
if thr.details is not None:
return thr.details
- return None
+ # Always return a name, as the protocol doesn't allow for nameless
+ # threads. Use the local thread number here... it doesn't matter
+ # without multi-inferior but in that case it might make more
+ # sense.
+ return f"Thread #{thr.num}"
-@request("threads")
+@request("threads", expect_stopped=False)
def threads(**args):
result = []
for thr in gdb.selected_inferior().threads():
- one_result = {
- "id": thr.global_num,
- }
- name = _thread_name(thr)
- if name is not None:
- one_result["name"] = name
- result.append(one_result)
+ result.append(
+ {
+ "id": thr.global_num,
+ "name": _thread_name(thr),
+ }
+ )
return {
"threads": result,
}
diff --git a/gdb/python/lib/gdb/dap/typecheck.py b/gdb/python/lib/gdb/dap/typecheck.py
index 55896cc..1496b67 100644
--- a/gdb/python/lib/gdb/dap/typecheck.py
+++ b/gdb/python/lib/gdb/dap/typecheck.py
@@ -1,4 +1,4 @@
-# Copyright 2023-2024 Free Software Foundation, Inc.
+# Copyright 2023-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py
index 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 7ccc4fe..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,72 +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:
- # 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 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
@@ -93,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
@@ -148,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
index 3be65fe..fcc72de 100644
--- a/gdb/python/lib/gdb/ptwrite.py
+++ b/gdb/python/lib/gdb/ptwrite.py
@@ -1,5 +1,5 @@
# Ptwrite utilities.
-# Copyright (C) 2023 Free Software Foundation, Inc.
+# Copyright (C) 2023-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gdb/python/lib/gdb/styling.py b/gdb/python/lib/gdb/styling.py
index 1c5394e..60c470f 100644
--- a/gdb/python/lib/gdb/styling.py
+++ b/gdb/python/lib/gdb/styling.py
@@ -1,5 +1,5 @@
# Styling related hooks.
-# Copyright (C) 2010-2024 Free Software Foundation, Inc.
+# Copyright (C) 2010-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ try:
from pygments import formatters, highlight, lexers
from pygments.filters import TokenMergeFilter
from pygments.token import Comment, Error, Text
+ from pygments.util import ClassNotFound
_formatter = None
@@ -31,10 +32,13 @@ try:
_formatter = formatters.TerminalFormatter()
return _formatter
- def colorize(filename, contents):
+ def colorize(filename, contents, lang):
# Don't want any errors.
try:
- lexer = lexers.get_lexer_for_filename(filename, stripnl=False)
+ try:
+ lexer = lexers.get_lexer_by_name(lang, stripnl=False)
+ except ClassNotFound:
+ lexer = lexers.get_lexer_for_filename(filename, stripnl=False)
formatter = get_formatter()
return highlight(contents, lexer, formatter).encode(
gdb.host_charset(), "backslashreplace"
@@ -76,7 +80,6 @@ try:
# ignore.
pass
- global _asm_lexers
if lexer_type not in _asm_lexers:
_asm_lexers[lexer_type] = lexers.get_lexer_by_name(lexer_type)
_asm_lexers[lexer_type].add_filter(HandleNasmComments())
@@ -94,7 +97,7 @@ try:
except ImportError:
- def colorize(filename, contents):
+ def colorize(filename, contents, lang):
return None
def colorize_disasm(content, gdbarch):
diff --git a/gdb/python/lib/gdb/types.py b/gdb/python/lib/gdb/types.py
index b4af59c..bac1fb9 100644
--- a/gdb/python/lib/gdb/types.py
+++ b/gdb/python/lib/gdb/types.py
@@ -1,5 +1,5 @@
# Type utilities.
-# Copyright (C) 2010-2024 Free Software Foundation, Inc.
+# Copyright (C) 2010-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
index bb0db79..3e1f756 100644
--- a/gdb/python/lib/gdb/unwinder.py
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2024 Free Software Foundation, Inc.
+# Copyright (C) 2015-2025 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gdb/python/lib/gdb/xmethod.py b/gdb/python/lib/gdb/xmethod.py
index 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)