diff --git a/mingw32/bin/gdb-add-index b/mingw32/bin/gdb-add-index
index fc10bb52b49..80bc4956358 100644
--- a/mingw32/bin/gdb-add-index
+++ b/mingw32/bin/gdb-add-index
@@ -2,7 +2,7 @@
# Add a .gdb_index section to a file.
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
diff --git a/mingw32/bin/gdb.exe b/mingw32/bin/gdb.exe
index 75e152e39cb..136fa4716df 100644
Binary files a/mingw32/bin/gdb.exe and b/mingw32/bin/gdb.exe differ
diff --git a/mingw32/bin/gdbserver.exe b/mingw32/bin/gdbserver.exe
index 3b2511e1d5f..290f9c9b33e 100644
Binary files a/mingw32/bin/gdbserver.exe and b/mingw32/bin/gdbserver.exe differ
diff --git a/mingw32/include/gdb/jit-reader.h b/mingw32/include/gdb/jit-reader.h
index 98c24af5f74..d305623de93 100644
--- a/mingw32/include/gdb/jit-reader.h
+++ b/mingw32/include/gdb/jit-reader.h
@@ -1,6 +1,6 @@
/* JIT declarations for GDB, the GNU Debugger.
- Copyright (C) 2011-2023 Free Software Foundation, Inc.
+ Copyright (C) 2011-2024 Free Software Foundation, Inc.
This file is part of GDB.
diff --git a/mingw32/share/gdb/python/gdb/FrameDecorator.py b/mingw32/share/gdb/python/gdb/FrameDecorator.py
index b6eb1ab1de0..82412de13f9 100644
--- a/mingw32/share/gdb/python/gdb/FrameDecorator.py
+++ b/mingw32/share/gdb/python/gdb/FrameDecorator.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 Free Software Foundation, Inc.
+# 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
@@ -251,7 +251,6 @@ def symbol(self):
class FrameVars(object):
-
"""Utility class to fetch and store frame local variables, or
frame arguments."""
diff --git a/mingw32/share/gdb/python/gdb/FrameIterator.py b/mingw32/share/gdb/python/gdb/FrameIterator.py
index 2cae7b59a0f..75176c38e43 100644
--- a/mingw32/share/gdb/python/gdb/FrameIterator.py
+++ b/mingw32/share/gdb/python/gdb/FrameIterator.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 Free Software Foundation, Inc.
+# 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
diff --git a/mingw32/share/gdb/python/gdb/__init__.py b/mingw32/share/gdb/python/gdb/__init__.py
index b3124369fe8..6c3e2419a40 100644
--- a/mingw32/share/gdb/python/gdb/__init__.py
+++ b/mingw32/share/gdb/python/gdb/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
@@ -13,12 +13,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import os
import signal
+import sys
import threading
import traceback
-import os
-import sys
-import _gdb
from contextlib import contextmanager
# Python 3 moved "reload"
@@ -27,7 +26,12 @@
else:
from imp import reload
-from _gdb import *
+import _gdb
+
+# Note that two indicators are needed here to silence flake8.
+from _gdb import * # noqa: F401,F403
+
+# isort: split
# Historically, gdb.events was always available, so ensure it's
# still available without an explicit import.
@@ -56,15 +60,14 @@ def writelines(self, iterable):
self.write(line)
def flush(self):
- flush(stream=self.stream)
+ _gdb.flush(stream=self.stream)
def write(self, s):
- write(s, stream=self.stream)
-
+ _gdb.write(s, stream=self.stream)
-sys.stdout = _GdbFile(STDOUT)
-sys.stderr = _GdbFile(STDERR)
+sys.stdout = _GdbFile(_gdb.STDOUT)
+sys.stderr = _GdbFile(_gdb.STDERR)
# Default prompt hook does nothing.
prompt_hook = None
@@ -84,6 +87,8 @@ def write(self, s):
frame_filters = {}
# Initial frame unwinders.
frame_unwinders = []
+# Initial missing debug handlers.
+missing_debug_handlers = []
def _execute_unwinders(pending_frame):
@@ -125,33 +130,6 @@ def _execute_unwinders(pending_frame):
return None
-def _execute_file(filepath):
- """This function is used to replace Python 2's PyRun_SimpleFile.
-
- Loads and executes the given file.
-
- We could use the runpy module, but its documentation says:
- "Furthermore, any functions and classes defined by the executed code are
- not guaranteed to work correctly after a runpy function has returned."
- """
- globals = sys.modules["__main__"].__dict__
- set_file = False
- # Set file (if not set) so that the imported file can use it (e.g. to
- # access file-relative paths). This matches what PyRun_SimpleFile does.
- if not hasattr(globals, "__file__"):
- globals["__file__"] = filepath
- set_file = True
- try:
- with open(filepath, "rb") as file:
- # We pass globals also as locals to match what Python does
- # in PyRun_SimpleFile.
- compiled = compile(file.read(), filepath, "exec")
- exec(compiled, globals, globals)
- finally:
- if set_file:
- del globals["__file__"]
-
-
# Convenience variable to GDB's python directory
PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
@@ -183,7 +161,7 @@ def _auto_load_packages():
reload(__import__(modname))
else:
__import__(modname)
- except:
+ except Exception:
sys.stderr.write(traceback.format_exc() + "\n")
@@ -210,7 +188,7 @@ def GdbSetPythonDirectory(dir):
def current_progspace():
"Return the current Progspace."
- return selected_inferior().progspace
+ return _gdb.selected_inferior().progspace
def objfiles():
@@ -247,14 +225,14 @@ def set_parameter(name, value):
value = "on"
else:
value = "off"
- execute("set " + name + " " + str(value), to_string=True)
+ _gdb.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 = parameter(name)
+ old_value = _gdb.parameter(name)
set_parameter(name, value)
try:
# Nothing that useful to return.
@@ -291,3 +269,42 @@ def start(self):
# threads.
with blocked_signals():
super().start()
+
+
+def _handle_missing_debuginfo(objfile):
+ """Internal function called from GDB to execute missing debug
+ handlers.
+
+ Run each of the currently registered, and enabled missing debug
+ handler objects for the current program space and then from the
+ global list. Stop after the first handler that returns a result
+ other than None.
+
+ Arguments:
+ objfile: A gdb.Objfile for which GDB could not find any debug
+ information.
+
+ Returns:
+ None: No debug information could be found for objfile.
+ False: A handler has done all it can with objfile, but no
+ debug information could be found.
+ True: Debug information might have been installed by a
+ handler, GDB should check again.
+ 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
+
+ for handler in missing_debug_handlers:
+ if handler.enabled:
+ result = handler(objfile)
+ if result is not None:
+ return result
+
+ return None
diff --git a/mingw32/share/gdb/python/gdb/command/__init__.py b/mingw32/share/gdb/python/gdb/command/__init__.py
index 5f369bfa2ea..f1b13bd00f2 100644
--- a/mingw32/share/gdb/python/gdb/command/__init__.py
+++ b/mingw32/share/gdb/python/gdb/command/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
diff --git a/mingw32/share/gdb/python/gdb/command/explore.py b/mingw32/share/gdb/python/gdb/command/explore.py
index 821bfb081ea..e359fa506fa 100644
--- a/mingw32/share/gdb/python/gdb/command/explore.py
+++ b/mingw32/share/gdb/python/gdb/command/explore.py
@@ -1,5 +1,5 @@
# GDB 'explore' command.
-# Copyright (C) 2012-2023 Free Software Foundation, Inc.
+# Copyright (C) 2012-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
diff --git a/mingw32/share/gdb/python/gdb/command/frame_filters.py b/mingw32/share/gdb/python/gdb/command/frame_filters.py
index a88de49cfea..4e1b3208d0e 100644
--- a/mingw32/share/gdb/python/gdb/command/frame_filters.py
+++ b/mingw32/share/gdb/python/gdb/command/frame_filters.py
@@ -1,5 +1,5 @@
# Frame-filter commands.
-# Copyright (C) 2013-2023 Free Software Foundation, Inc.
+# 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
@@ -17,6 +17,7 @@
"""GDB commands for working with frame-filters."""
import sys
+
import gdb
import gdb.frames
@@ -445,7 +446,7 @@ def complete(self, text, word):
if text.count(" ") == 0:
return _complete_frame_filter_list(text, word, False)
else:
- printer_list = frame._return_list(text.split()[0].rstrip())
+ printer_list = gdb.frames.return_list(text.split()[0].rstrip())
return _complete_frame_filter_name(word, printer_list)
def invoke(self, arg, from_tty):
@@ -454,20 +455,15 @@ def invoke(self, arg, from_tty):
return
filter_name = command_tuple[1]
list_name = command_tuple[0]
- try:
- priority = self.get_filter_priority(list_name, filter_name)
- except Exception:
- e = sys.exc_info()[1]
- print("Error printing filter priority for '" + name + "':" + str(e))
- else:
- print(
- "Priority of filter '"
- + filter_name
- + "' in list '"
- + list_name
- + "' is: "
- + str(priority)
- )
+ priority = self.get_filter_priority(list_name, filter_name)
+ print(
+ "Priority of filter '"
+ + filter_name
+ + "' in list '"
+ + list_name
+ + "' is: "
+ + str(priority)
+ )
# Register commands
diff --git a/mingw32/share/gdb/python/gdb/command/missing_debug.py b/mingw32/share/gdb/python/gdb/command/missing_debug.py
new file mode 100644
index 00000000000..313b88cf8c3
--- /dev/null
+++ b/mingw32/share/gdb/python/gdb/command/missing_debug.py
@@ -0,0 +1,227 @@
+# Missing debug related commands.
+#
+# Copyright 2023-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 .
+
+import re
+
+import gdb
+
+
+def validate_regexp(exp, idstring):
+ """Compile exp into a compiler regular expression object.
+
+ Arguments:
+ exp: The string to compile into a re.Pattern object.
+ idstring: A string, what exp is a regexp for.
+
+ Returns:
+ A re.Pattern object representing exp.
+
+ 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.
+
+ Arguments:
+ arg: The arguments to the command. The format is:
+ [locus-regexp [name-regexp]]
+
+ Returns:
+ A 2-tuple of compiled regular expressions.
+
+ Raises:
+ SyntaxError: an error processing ARG
+ """
+ argv = gdb.string_to_argv(arg)
+ argc = len(argv)
+ if argc > 2:
+ raise SyntaxError("Too many arguments.")
+ locus_regexp = ""
+ name_regexp = ""
+ if argc >= 1:
+ locus_regexp = argv[0]
+ if argc >= 2:
+ name_regexp = argv[1]
+ return (
+ validate_regexp(locus_regexp, "locus"),
+ validate_regexp(name_regexp, "handler"),
+ )
+
+
+class InfoMissingDebugHanders(gdb.Command):
+ """GDB command to list missing debug handlers.
+
+ Usage: info missing-debug-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
+ loci are listed. A locus can be 'global', 'progspace' to list
+ the handlers from the current progspace, or a regular expression
+ matching filenames of progspaces.
+
+ NAME-REGEXP is a regular expression to filter missing debug
+ 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 list_handlers(self, title, handlers, name_re):
+ """Lists the missing debug handlers whose name matches regexp.
+
+ Arguments:
+ title: The line to print before the list.
+ handlers: The list of the missing debug handlers.
+ name_re: handler name filter.
+ """
+ if not handlers:
+ return
+ print(title)
+ for handler in handlers:
+ 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)
+
+ 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
+ )
+
+ for progspace in gdb.progspaces():
+ filename = progspace.filename or ""
+ if locus_re.match(filename):
+ if filename == "":
+ if progspace == gdb.current_progspace():
+ msg = "Current Progspace:"
+ else:
+ msg = "Progspace :"
+ else:
+ msg = "Progspace %s:" % filename
+ self.list_handlers(
+ msg,
+ progspace.missing_debug_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)
+
+
+def do_enable_handler1(handlers, name_re, flag):
+ """Enable/disable missing debug handlers whose names match given regex.
+
+ Arguments:
+ handlers: The list of missing debug handlers.
+ name_re: Handler name filter.
+ flag: A boolean indicating if we should enable or disable.
+
+ Returns:
+ The number of handlers affected.
+ """
+ total = 0
+ for handler in handlers:
+ 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)
+ total = 0
+ if locus_re.match("global"):
+ total += do_enable_handler1(gdb.missing_debug_handlers, name_re, flag)
+ if locus_re.match("progspace") and locus_re.pattern != "":
+ total += do_enable_handler1(
+ gdb.current_progspace().missing_debug_handlers, name_re, flag
+ )
+ 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)
+ print(
+ "%d missing debug handler%s %s"
+ % (total, "" if total == 1 else "s", "enabled" if flag else "disabled")
+ )
+
+
+class EnableMissingDebugHandler(gdb.Command):
+ """GDB command to enable missing debug handlers.
+
+ Usage: enable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+
+ LOCUS-REGEXP is a regular expression specifying the handlers to
+ enable. It can be 'global', 'progspace' for the current
+ progspace, or the filename for a file associated with a progspace.
+
+ NAME_REGEXP is a regular expression to filter handler names. If
+ this omitted for a specified locus, then all registered handlers
+ in the locus are affected.
+ """
+
+ def __init__(self):
+ super().__init__("enable missing-debug-handler", gdb.COMMAND_FILES)
+
+ def invoke(self, arg, from_tty):
+ """GDB calls this to perform the command."""
+ do_enable_handler(arg, True)
+
+
+class DisableMissingDebugHandler(gdb.Command):
+ """GDB command to disable missing debug handlers.
+
+ Usage: disable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+
+ LOCUS-REGEXP is a regular expression specifying the handlers to
+ enable. It can be 'global', 'progspace' for the current
+ progspace, or the filename for a file associated with a progspace.
+
+ NAME_REGEXP is a regular expression to filter handler names. If
+ this omitted for a specified locus, then all registered handlers
+ in the locus are affected.
+ """
+
+ def __init__(self):
+ super().__init__("disable missing-debug-handler", gdb.COMMAND_FILES)
+
+ def invoke(self, arg, from_tty):
+ """GDB calls this to perform the command."""
+ do_enable_handler(arg, False)
+
+
+def register_missing_debug_handler_commands():
+ """Installs the missing debug handler commands."""
+ InfoMissingDebugHanders()
+ EnableMissingDebugHandler()
+ DisableMissingDebugHandler()
+
+
+register_missing_debug_handler_commands()
diff --git a/mingw32/share/gdb/python/gdb/command/pretty_printers.py b/mingw32/share/gdb/python/gdb/command/pretty_printers.py
index a3b582e96d6..cb9b9f35f9f 100644
--- a/mingw32/share/gdb/python/gdb/command/pretty_printers.py
+++ b/mingw32/share/gdb/python/gdb/command/pretty_printers.py
@@ -1,5 +1,5 @@
# Pretty-printer commands.
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
@@ -17,9 +17,10 @@
"""GDB commands for working with pretty-printers."""
import copy
-import gdb
import re
+import gdb
+
def parse_printer_regexps(arg):
"""Internal utility to parse a pretty-printer command argv.
diff --git a/mingw32/share/gdb/python/gdb/command/prompt.py b/mingw32/share/gdb/python/gdb/command/prompt.py
index 775fa1a9a54..2cfb25db30c 100644
--- a/mingw32/share/gdb/python/gdb/command/prompt.py
+++ b/mingw32/share/gdb/python/gdb/command/prompt.py
@@ -1,5 +1,5 @@
# Extended prompt.
-# Copyright (C) 2011-2023 Free Software Foundation, Inc.
+# Copyright (C) 2011-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
@@ -21,7 +21,6 @@
class _ExtendedPrompt(gdb.Parameter):
-
"""Set the extended prompt.
Usage: set extended-prompt VALUE
diff --git a/mingw32/share/gdb/python/gdb/command/type_printers.py b/mingw32/share/gdb/python/gdb/command/type_printers.py
index 35e62016f59..a2be226850d 100644
--- a/mingw32/share/gdb/python/gdb/command/type_printers.py
+++ b/mingw32/share/gdb/python/gdb/command/type_printers.py
@@ -1,5 +1,5 @@
# Type printer commands.
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
@@ -15,6 +15,7 @@
# along with this program. If not, see .
import copy
+
import gdb
"""GDB commands for working with type-printers."""
diff --git a/mingw32/share/gdb/python/gdb/command/unwinders.py b/mingw32/share/gdb/python/gdb/command/unwinders.py
index 68087aa3266..b863b333aa0 100644
--- a/mingw32/share/gdb/python/gdb/command/unwinders.py
+++ b/mingw32/share/gdb/python/gdb/command/unwinders.py
@@ -1,5 +1,5 @@
# Unwinder commands.
-# Copyright 2015-2023 Free Software Foundation, Inc.
+# Copyright 2015-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
@@ -14,9 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
import re
+import gdb
+
def validate_regexp(exp, idstring):
try:
diff --git a/mingw32/share/gdb/python/gdb/command/xmethods.py b/mingw32/share/gdb/python/gdb/command/xmethods.py
index 4bf89696e49..f7862271858 100644
--- a/mingw32/share/gdb/python/gdb/command/xmethods.py
+++ b/mingw32/share/gdb/python/gdb/command/xmethods.py
@@ -1,5 +1,5 @@
# Xmethod commands.
-# Copyright 2013-2023 Free Software Foundation, Inc.
+# Copyright 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
@@ -14,9 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
import re
+import gdb
+
"""GDB commands for working with xmethods."""
diff --git a/mingw32/share/gdb/python/gdb/dap/__init__.py b/mingw32/share/gdb/python/gdb/dap/__init__.py
index 689c2049eec..51b95468a70 100644
--- a/mingw32/share/gdb/python/gdb/dap/__init__.py
+++ b/mingw32/share/gdb/python/gdb/dap/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -14,26 +14,31 @@
# along with this program. If not, see .
import os
-import gdb
# This must come before other DAP imports.
from . import startup
-# Load modules that define commands.
-from . import breakpoint
-from . import bt
-from . import disassemble
-from . import evaluate
-from . import launch
-from . import locations
-from . import memory
-from . import modules
-from . import next
-from . import pause
-from . import scopes
-from . import sources
-from . import threads
+# isort: split
+# Load modules that define commands. These imports intentionally
+# ignore the unused import warning, as these modules are being loaded
+# for their side effects -- namely, registering DAP commands with the
+# server object. "F401" is the flake8 "imported but unused" code.
+from . import breakpoint # noqa: F401
+from . import bt # noqa: F401
+from . import disassemble # noqa: F401
+from . import evaluate # noqa: F401
+from . import launch # noqa: F401
+from . import locations # noqa: F401
+from . import memory # noqa: F401
+from . import modules # noqa: F401
+from . import next # noqa: F401
+from . import pause # noqa: F401
+from . import scopes # noqa: F401
+from . import sources # noqa: F401
+from . import threads # noqa: F401
+
+# isort: split
from .server import Server
@@ -69,5 +74,23 @@ def run():
os.close(wfd)
# Note the inferior output is opened in text mode.
+ global server
server = Server(open(saved_in, "rb"), open(saved_out, "wb"), open(rfd, "r"))
- startup.start_dap(server.main_loop)
+
+
+# Whether the interactive session has started.
+session_started = False
+
+
+def pre_command_loop():
+ """DAP's pre_command_loop interpreter hook. This is called by the GDB DAP
+ interpreter."""
+ global session_started
+ if not session_started:
+ # The pre_command_loop interpreter hook can be called several times.
+ # The first time it's called, it means we're starting an interactive
+ # session.
+ session_started = True
+ startup.thread_log("starting DAP server")
+ global server
+ startup.start_dap(server.main_loop)
diff --git a/mingw32/share/gdb/python/gdb/dap/breakpoint.py b/mingw32/share/gdb/python/gdb/dap/breakpoint.py
index 33aa18e65bc..e60265b2f69 100644
--- a/mingw32/share/gdb/python/gdb/dap/breakpoint.py
+++ b/mingw32/share/gdb/python/gdb/dap/breakpoint.py
@@ -1,4 +1,4 @@
-# Copyright 2022, 2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,32 +13,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
-import os
import re
-
from contextlib import contextmanager
# These are deprecated in 3.9, but required in older versions.
from typing import Optional, Sequence
-from .server import request, capability, send_event
+import gdb
+
+from .server import capability, request, send_event
from .sources import make_source
-from .startup import in_gdb_thread, log_stack
+from .startup import DAPException, LogLevel, in_gdb_thread, log_stack, parse_and_eval
from .typecheck import type_check
-
-@in_gdb_thread
-def _bp_modified(event):
- send_event(
- "breakpoint",
- {
- "reason": "changed",
- "breakpoint": _breakpoint_descriptor(event),
- },
- )
-
-
# True when suppressing new breakpoint events.
_suppress_bp = False
@@ -47,11 +34,25 @@ def _bp_modified(event):
def suppress_new_breakpoint_event():
"""Return a new context manager that suppresses new breakpoint events."""
global _suppress_bp
+ saved = _suppress_bp
_suppress_bp = True
try:
yield None
finally:
- _suppress_bp = False
+ _suppress_bp = saved
+
+
+@in_gdb_thread
+def _bp_modified(event):
+ global _suppress_bp
+ if not _suppress_bp:
+ send_event(
+ "breakpoint",
+ {
+ "reason": "changed",
+ "breakpoint": _breakpoint_descriptor(event),
+ },
+ )
@in_gdb_thread
@@ -69,13 +70,15 @@ def _bp_created(event):
@in_gdb_thread
def _bp_deleted(event):
- send_event(
- "breakpoint",
- {
- "reason": "removed",
- "breakpoint": _breakpoint_descriptor(event),
- },
- )
+ global _suppress_bp
+ if not _suppress_bp:
+ send_event(
+ "breakpoint",
+ {
+ "reason": "removed",
+ "breakpoint": _breakpoint_descriptor(event),
+ },
+ )
gdb.events.breakpoint_created.connect(_bp_created)
@@ -96,11 +99,10 @@ def _breakpoint_descriptor(bp):
"Return the Breakpoint object descriptor given a gdb Breakpoint."
result = {
"id": bp.number,
- # We always use True here, because this field just indicates
- # that breakpoint creation was successful -- and if we have a
- # breakpoint, the creation succeeded.
- "verified": True,
+ "verified": not bp.pending,
}
+ if bp.pending:
+ result["reason"] = "pending"
if bp.locations:
# Just choose the first location, because DAP doesn't allow
# multiple locations. See
@@ -113,7 +115,7 @@ def _breakpoint_descriptor(bp):
result.update(
{
- "source": make_source(filename, os.path.basename(filename)),
+ "source": make_source(filename),
"line": line,
}
)
@@ -145,50 +147,55 @@ def _set_breakpoints_callback(kind, specs, creator):
saved_map = {}
breakpoint_map[kind] = {}
result = []
- for spec in specs:
- # It makes sense to reuse a breakpoint even if the condition
- # or ignore count differs, so remove these entries from the
- # spec first.
- (condition, hit_condition) = _remove_entries(spec, "condition", "hitCondition")
- keyspec = frozenset(spec.items())
-
- # Create or reuse a breakpoint. If asked, set the condition
- # or the ignore count. Catch errors coming from gdb and
- # report these as an "unverified" breakpoint.
- bp = None
- try:
- if keyspec in saved_map:
- bp = saved_map.pop(keyspec)
- else:
- with suppress_new_breakpoint_event():
+ with suppress_new_breakpoint_event():
+ for spec in specs:
+ # It makes sense to reuse a breakpoint even if the condition
+ # or ignore count differs, so remove these entries from the
+ # spec first.
+ (condition, hit_condition) = _remove_entries(
+ spec, "condition", "hitCondition"
+ )
+ keyspec = frozenset(spec.items())
+
+ # Create or reuse a breakpoint. If asked, set the condition
+ # or the ignore count. Catch errors coming from gdb and
+ # report these as an "unverified" breakpoint.
+ bp = None
+ try:
+ if keyspec in saved_map:
+ bp = saved_map.pop(keyspec)
+ else:
bp = creator(**spec)
- bp.condition = condition
- if hit_condition is None:
- bp.ignore_count = 0
- else:
- bp.ignore_count = int(
- gdb.parse_and_eval(hit_condition, global_context=True)
+ bp.condition = condition
+ if hit_condition is None:
+ bp.ignore_count = 0
+ else:
+ bp.ignore_count = int(
+ parse_and_eval(hit_condition, global_context=True)
+ )
+
+ # Reaching this spot means success.
+ breakpoint_map[kind][keyspec] = bp
+ result.append(_breakpoint_descriptor(bp))
+ # Exceptions other than gdb.error are possible here.
+ except Exception as e:
+ # Don't normally want to see this, as it interferes with
+ # the test suite.
+ log_stack(LogLevel.FULL)
+ # Maybe the breakpoint was made but setting an attribute
+ # failed. We still want this to fail.
+ if bp is not None:
+ bp.delete()
+ # Breakpoint creation failed.
+ result.append(
+ {
+ "verified": False,
+ "reason": "failed",
+ "message": str(e),
+ }
)
- # Reaching this spot means success.
- breakpoint_map[kind][keyspec] = bp
- result.append(_breakpoint_descriptor(bp))
- # Exceptions other than gdb.error are possible here.
- except Exception as e:
- log_stack()
- # Maybe the breakpoint was made but setting an attribute
- # failed. We still want this to fail.
- if bp is not None:
- bp.delete()
- # Breakpoint creation failed.
- result.append(
- {
- "verified": False,
- "message": str(e),
- }
- )
-
# Delete any breakpoints that were not reused.
for entry in saved_map.values():
entry.delete()
@@ -212,6 +219,7 @@ def stop(self):
# have already been stripped by the placement of the
# regex capture in the 'split' call.
try:
+ # No real need to use the DAP parse_and_eval here.
val = gdb.parse_and_eval(item)
output += str(val)
except Exception as e:
@@ -268,7 +276,6 @@ def _rewrite_src_breakpoint(
}
-# FIXME we do not specify a type for 'source'.
@request("setBreakpoints")
@capability("supportsHitConditionalBreakpoints")
@capability("supportsConditionalBreakpoints")
@@ -360,12 +367,13 @@ def _catch_exception(filterId, **args):
if filterId in ("assert", "exception", "throw", "rethrow", "catch"):
cmd = "-catch-" + filterId
else:
- raise Exception("Invalid exception filterID: " + str(filterId))
+ raise DAPException("Invalid exception filterID: " + str(filterId))
result = gdb.execute_mi(cmd)
# A little lame that there's no more direct way.
for bp in gdb.breakpoints():
if bp.number == result["bkptno"]:
return bp
+ # Not a DAPException because this is definitely unexpected.
raise Exception("Could not find catchpoint after creating")
diff --git a/mingw32/share/gdb/python/gdb/dap/bt.py b/mingw32/share/gdb/python/gdb/dap/bt.py
index a770b254837..668bcc7ce23 100644
--- a/mingw32/share/gdb/python/gdb/dap/bt.py
+++ b/mingw32/share/gdb/python/gdb/dap/bt.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,16 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
-import os
-
# This is deprecated in 3.9, but required in older versions.
from typing import Optional
+import gdb
+
from .frames import dap_frame_generator
from .modules import module_id
from .scopes import symbol_value
-from .server import request, capability
+from .server import capability, request
from .sources import make_source
from .startup import in_gdb_thread
from .state import set_thread
@@ -97,7 +96,7 @@ def _backtrace(thread_id, levels, startFrame, stack_format):
name += ", module " + objfile.username
filename = current_frame.filename()
if filename is not None:
- newframe["source"] = make_source(filename, os.path.basename(filename))
+ newframe["source"] = make_source(filename)
newframe["name"] = name
frames.append(newframe)
# Note that we do not calculate totalFrames here. Its absence
diff --git a/mingw32/share/gdb/python/gdb/dap/disassemble.py b/mingw32/share/gdb/python/gdb/dap/disassemble.py
index 069549eb7f8..d65790a40b0 100644
--- a/mingw32/share/gdb/python/gdb/dap/disassemble.py
+++ b/mingw32/share/gdb/python/gdb/dap/disassemble.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -15,32 +15,51 @@
import gdb
-from .server import request, capability
-from .startup import in_gdb_thread
+from .server import capability, request
+from .sources import make_source
-@in_gdb_thread
-def _disassemble(pc, skip_insns, count):
- inf = gdb.selected_inferior()
- try:
- arch = gdb.selected_frame().architecture()
- except gdb.error:
- # Maybe there was no frame.
- arch = inf.architecture()
- result = []
- total_count = skip_insns + count
- for elt in arch.disassemble(pc, count=total_count)[skip_insns:]:
- mem = inf.read_memory(elt["addr"], elt["length"])
- result.append(
- {
- "address": hex(elt["addr"]),
- "instruction": elt["asm"],
- "instructionBytes": mem.hex(),
- }
- )
- return {
- "instructions": result,
- }
+# This tracks labels associated with a disassembly request and helps
+# with updating individual instructions.
+class _BlockTracker:
+ def __init__(self):
+ # 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 = {}
+ # List of blocks that have already been handled. Note that
+ # blocks aren't hashable so a set is not used.
+ self.blocks = []
+
+ # 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:
+ return
+ self.blocks.append(block)
+ if block.function is not None:
+ 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
+ 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]
+ sal = gdb.find_pc_line(pc)
+ if sal.symtab is not None:
+ if sal.line != 0:
+ result["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
+ # supply it.
+ result["location"] = make_source(sal.symtab.filename)
@request("disassemble")
@@ -54,4 +73,24 @@ def disassemble(
**extra
):
pc = int(memoryReference, 0) + offset
- return _disassemble(pc, instructionOffset, instructionCount)
+ inf = gdb.selected_inferior()
+ try:
+ arch = gdb.selected_frame().architecture()
+ except gdb.error:
+ # Maybe there was no frame.
+ arch = inf.architecture()
+ tracker = _BlockTracker()
+ result = []
+ total_count = instructionOffset + instructionCount
+ for elt in arch.disassemble(pc, count=total_count)[instructionOffset:]:
+ mem = inf.read_memory(elt["addr"], elt["length"])
+ insn = {
+ "address": hex(elt["addr"]),
+ "instruction": elt["asm"],
+ "instructionBytes": mem.hex(),
+ }
+ tracker.add_pc(elt["addr"], insn)
+ result.append(insn)
+ return {
+ "instructions": result,
+ }
diff --git a/mingw32/share/gdb/python/gdb/dap/evaluate.py b/mingw32/share/gdb/python/gdb/dap/evaluate.py
index 67e103e2ca7..34843f423d8 100644
--- a/mingw32/share/gdb/python/gdb/dap/evaluate.py
+++ b/mingw32/share/gdb/python/gdb/dap/evaluate.py
@@ -1,4 +1,4 @@
-# Copyright 2022, 2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,15 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
-
# This is deprecated in 3.9, but required in older versions.
from typing import Optional
+import gdb
+
from .frames import select_frame
-from .server import capability, request, client_bool_capability
-from .startup import in_gdb_thread
-from .varref import find_variable, VariableReference, apply_format
+from .server import capability, client_bool_capability, request
+from .startup import DAPException, in_gdb_thread, parse_and_eval
+from .varref import VariableReference, apply_format, find_variable
class EvaluateResult(VariableReference):
@@ -37,7 +37,7 @@ def _evaluate(expr, frame_id, value_format):
if frame_id is not None:
select_frame(frame_id)
global_context = False
- val = gdb.parse_and_eval(expr, global_context=global_context)
+ val = parse_and_eval(expr, global_context=global_context)
ref = EvaluateResult(val)
return ref.to_object()
@@ -57,20 +57,6 @@ def __init__(self, value):
super().__init__(None, value, "value")
-# Helper function to perform an assignment.
-@in_gdb_thread
-def _set_expression(expression, value, frame_id, value_format):
- with apply_format(value_format):
- global_context = True
- if frame_id is not None:
- select_frame(frame_id)
- global_context = False
- lhs = gdb.parse_and_eval(expression, global_context=global_context)
- rhs = gdb.parse_and_eval(value, global_context=global_context)
- lhs.assign(rhs)
- return _SetResult(lhs).to_object()
-
-
# Helper function to evaluate a gdb command in a certain frame.
@in_gdb_thread
def _repl(command, frame_id):
@@ -103,15 +89,7 @@ def eval_request(
# Ignore the format for repl evaluation.
return _repl(expression, frameId)
else:
- raise Exception('unknown evaluate context "' + context + '"')
-
-
-@in_gdb_thread
-def _variables(ref, start, count, value_format):
- with apply_format(value_format):
- var = find_variable(ref)
- children = var.fetch_children(start, count)
- return [x.to_object() for x in children]
+ raise DAPException('unknown evaluate context "' + context + '"')
@request("variables")
@@ -125,7 +103,10 @@ def variables(
if not client_bool_capability("supportsVariablePaging"):
start = 0
count = 0
- return {"variables": _variables(variablesReference, start, count, format)}
+ with apply_format(format):
+ var = find_variable(variablesReference)
+ children = var.fetch_children(start, count)
+ return {"variables": [x.to_object() for x in children]}
@capability("supportsSetExpression")
@@ -133,18 +114,15 @@ def variables(
def set_expression(
*, expression: str, value: str, frameId: Optional[int] = None, format=None, **args
):
- return _set_expression(expression, value, frameId, format)
-
-
-# Helper function to perform an assignment.
-@in_gdb_thread
-def _set_variable(ref, name, value, value_format):
- with apply_format(value_format):
- var = find_variable(ref)
- lhs = var.find_child_by_name(name)
- rhs = gdb.parse_and_eval(value)
+ with apply_format(format):
+ global_context = True
+ if frameId is not None:
+ select_frame(frameId)
+ global_context = False
+ lhs = parse_and_eval(expression, global_context=global_context)
+ rhs = parse_and_eval(value, global_context=global_context)
lhs.assign(rhs)
- return lhs.to_object()
+ return _SetResult(lhs).to_object()
@capability("supportsSetVariable")
@@ -152,4 +130,9 @@ def _set_variable(ref, name, value, value_format):
def set_variable(
*, variablesReference: int, name: str, value: str, format=None, **args
):
- return _set_variable(variablesReference, name, value, format)
+ with apply_format(format):
+ var = find_variable(variablesReference)
+ lhs = var.find_child_by_name(name)
+ rhs = parse_and_eval(value)
+ lhs.assign(rhs)
+ return lhs.to_object()
diff --git a/mingw32/share/gdb/python/gdb/dap/events.py b/mingw32/share/gdb/python/gdb/dap/events.py
index 663b67ea24e..276d3145b9a 100644
--- a/mingw32/share/gdb/python/gdb/dap/events.py
+++ b/mingw32/share/gdb/python/gdb/dap/events.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,13 +13,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import enum
import gdb
+from .modules import is_module, make_module
+from .scopes import set_finish_value
from .server import send_event
from .startup import exec_and_log, in_gdb_thread, log
-from .modules import is_module, make_module
-
# True when the inferior is thought to be running, False otherwise.
# This may be accessed from any thread, which can be racy. However,
@@ -44,8 +43,43 @@ def _on_exit(event):
send_event("terminated")
+# When None, a "process" event has already been sent. When a string,
+# it is the "startMethod" for that event.
+_process_event_kind = None
+
+
+@in_gdb_thread
+def send_process_event_once():
+ global _process_event_kind
+ if _process_event_kind is not None:
+ inf = gdb.selected_inferior()
+ is_local = inf.connection.type == "native"
+ data = {
+ "isLocalProcess": is_local,
+ "startMethod": _process_event_kind,
+ # Could emit 'pointerSize' here too if we cared to.
+ }
+ if inf.progspace.filename:
+ data["name"] = inf.progspace.filename
+ if is_local:
+ data["systemProcessId"] = inf.pid
+ send_event("process", data)
+ _process_event_kind = None
+
+
+@in_gdb_thread
+def expect_process(reason):
+ """Indicate that DAP is starting or attaching to a process.
+
+ REASON is the "startMethod" to include in the "process" event.
+ """
+ global _process_event_kind
+ _process_event_kind = reason
+
+
@in_gdb_thread
def thread_event(event, reason):
+ send_process_event_once()
send_event(
"thread",
{
@@ -81,6 +115,7 @@ def _new_objfile(event):
@in_gdb_thread
def _objfile_removed(event):
+ send_process_event_once()
if is_module(event.objfile):
send_event(
"module",
@@ -112,48 +147,100 @@ def _cont(event):
)
-class StopKinds(enum.Enum):
- # The values here are chosen to follow the DAP spec.
- STEP = "step"
- BREAKPOINT = "breakpoint"
- PAUSE = "pause"
- EXCEPTION = "exception"
+_expected_stop_reason = None
-_expected_stop = None
+@in_gdb_thread
+def expect_stop(reason: str):
+ """Indicate that the next stop should be for REASON."""
+ global _expected_stop_reason
+ _expected_stop_reason = reason
+
+
+_expected_pause = False
@in_gdb_thread
-def exec_and_expect_stop(cmd, reason):
- """Indicate that a stop is expected, then execute CMD"""
- global _expected_stop
- _expected_stop = reason
- if reason != StopKinds.PAUSE:
- global _suppress_cont
- _suppress_cont = True
+def exec_and_expect_stop(cmd, expected_pause=False):
+ """A wrapper for exec_and_log that sets the continue-suppression flag.
+
+ When EXPECTED_PAUSE is True, a stop that looks like a pause (e.g.,
+ a SIGINT) will be reported as "pause" instead.
+ """
+ global _expected_pause
+ _expected_pause = expected_pause
+ global _suppress_cont
+ # If we're expecting a pause, then we're definitely not
+ # continuing.
+ _suppress_cont = not expected_pause
# FIXME if the call fails should we clear _suppress_cont?
exec_and_log(cmd)
+# Map from gdb stop reasons to DAP stop reasons. Some of these can't
+# be seen ordinarily in DAP -- only if the client lets the user toggle
+# some settings (e.g. stop-on-solib-events) or enter commands (e.g.,
+# 'until').
+stop_reason_map = {
+ "breakpoint-hit": "breakpoint",
+ "watchpoint-trigger": "data breakpoint",
+ "read-watchpoint-trigger": "data breakpoint",
+ "access-watchpoint-trigger": "data breakpoint",
+ "function-finished": "step",
+ "location-reached": "step",
+ "watchpoint-scope": "data breakpoint",
+ "end-stepping-range": "step",
+ "exited-signalled": "exited",
+ "exited": "exited",
+ "exited-normally": "exited",
+ "signal-received": "signal",
+ "solib-event": "solib",
+ "fork": "fork",
+ "vfork": "vfork",
+ "syscall-entry": "syscall-entry",
+ "syscall-return": "syscall-return",
+ "exec": "exec",
+ "no-history": "no-history",
+}
+
+
@in_gdb_thread
def _on_stop(event):
global inferior_running
inferior_running = False
+
log("entering _on_stop: " + repr(event))
- global _expected_stop
+ if hasattr(event, "details"):
+ log(" details: " + repr(event.details))
obj = {
"threadId": gdb.selected_thread().global_num,
"allThreadsStopped": True,
}
if isinstance(event, gdb.BreakpointEvent):
- # Ignore the expected stop, we hit a breakpoint instead.
- _expected_stop = StopKinds.BREAKPOINT
obj["hitBreakpointIds"] = [x.number for x in event.breakpoints]
- elif _expected_stop is None:
- # FIXME what is even correct here
- _expected_stop = StopKinds.EXCEPTION
- obj["reason"] = _expected_stop.value
- _expected_stop = None
+ if hasattr(event, "details") and "finish-value" in event.details:
+ set_finish_value(event.details["finish-value"])
+
+ global _expected_pause
+ global _expected_stop_reason
+ if _expected_stop_reason is not None:
+ obj["reason"] = _expected_stop_reason
+ _expected_stop_reason = None
+ elif "reason" not in event.details:
+ # This can only really happen via a "repl" evaluation of
+ # something like "attach". In this case just emit a generic
+ # stop.
+ obj["reason"] = "stopped"
+ elif (
+ _expected_pause
+ and event.details["reason"] == "signal-received"
+ and event.details["signal-name"] in ("SIGINT", "SIGSTOP")
+ ):
+ obj["reason"] = "pause"
+ else:
+ global stop_reason_map
+ obj["reason"] = stop_reason_map[event.details["reason"]]
+ _expected_pause = False
send_event("stopped", obj)
@@ -168,13 +255,26 @@ def _on_stop(event):
@in_gdb_thread
def _on_inferior_call(event):
global _infcall_was_running
+ global inferior_running
if isinstance(event, gdb.InferiorCallPreEvent):
_infcall_was_running = inferior_running
if not _infcall_was_running:
_cont(None)
else:
- if not _infcall_was_running:
- _on_stop(None)
+ # If the inferior is already marked as stopped here, then that
+ # means that the call caused some other stop, and we don't
+ # want to double-report it.
+ if not _infcall_was_running and inferior_running:
+ inferior_running = False
+ obj = {
+ "threadId": gdb.selected_thread().global_num,
+ "allThreadsStopped": True,
+ # DAP says any string is ok.
+ "reason": "function call",
+ }
+ global _expected_pause
+ _expected_pause = False
+ send_event("stopped", obj)
gdb.events.stop.connect(_on_stop)
diff --git a/mingw32/share/gdb/python/gdb/dap/frames.py b/mingw32/share/gdb/python/gdb/dap/frames.py
index 669e73f34c5..07a4e3ea793 100644
--- a/mingw32/share/gdb/python/gdb/dap/frames.py
+++ b/mingw32/share/gdb/python/gdb/dap/frames.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,14 +13,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
import itertools
+import gdb
from gdb.frames import frame_iterator
from .startup import in_gdb_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
# hashable.
diff --git a/mingw32/share/gdb/python/gdb/dap/io.py b/mingw32/share/gdb/python/gdb/dap/io.py
index 2f3a2351925..03031a72393 100644
--- a/mingw32/share/gdb/python/gdb/dap/io.py
+++ b/mingw32/share/gdb/python/gdb/dap/io.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -15,30 +15,44 @@
import json
-from .startup import start_thread, send_gdb, log
+from .startup import LogLevel, log, log_stack, start_thread
def read_json(stream):
"""Read a JSON-RPC message from STREAM.
- The decoded object is returned."""
- # First read and parse the header.
- content_length = None
- while True:
- line = stream.readline()
- line = line.strip()
- if line == b"":
- break
- if line.startswith(b"Content-Length:"):
- line = line[15:].strip()
- content_length = int(line)
- continue
- log("IGNORED: <<<%s>>>" % line)
- data = bytes()
- while len(data) < content_length:
- new_data = stream.read(content_length - len(data))
- data += new_data
- result = json.loads(data)
- return result
+ The decoded object is returned.
+ None is returned on EOF."""
+ try:
+ # First read and parse the header.
+ content_length = None
+ while True:
+ line = stream.readline()
+ # If the line is empty, we hit EOF.
+ if len(line) == 0:
+ log("EOF")
+ return None
+ line = line.strip()
+ if line == b"":
+ break
+ if line.startswith(b"Content-Length:"):
+ line = line[15:].strip()
+ content_length = int(line)
+ continue
+ log("IGNORED: <<<%s>>>" % line)
+ data = bytes()
+ while len(data) < content_length:
+ new_data = stream.read(content_length - len(data))
+ # Maybe we hit EOF.
+ if len(new_data) == 0:
+ log("EOF after reading the header")
+ return None
+ data += new_data
+ return json.loads(data)
+ except OSError:
+ # Reading can also possibly throw an exception. Treat this as
+ # EOF.
+ log_stack(LogLevel.FULL)
+ return None
def start_json_writer(stream, queue):
@@ -54,7 +68,6 @@ def _json_writer():
# This is an exit request. The stream is already
# flushed, so all that's left to do is request an
# exit.
- send_gdb("quit")
break
obj["seq"] = seq
seq = seq + 1
@@ -66,4 +79,4 @@ def _json_writer():
stream.write(body_bytes)
stream.flush()
- start_thread("JSON writer", _json_writer)
+ return start_thread("JSON writer", _json_writer)
diff --git a/mingw32/share/gdb/python/gdb/dap/launch.py b/mingw32/share/gdb/python/gdb/dap/launch.py
index ab704c7a7cc..2674e02eac3 100644
--- a/mingw32/share/gdb/python/gdb/dap/launch.py
+++ b/mingw32/share/gdb/python/gdb/dap/launch.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,23 +13,29 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
-
# These are deprecated in 3.9, but required in older versions.
from typing import Mapping, Optional, Sequence
-from .events import exec_and_expect_stop
-from .server import request, capability
-from .startup import in_gdb_thread, exec_and_log
-
+import gdb
-# The program being launched, or None. This should only be accessed
-# from the gdb thread.
-_program = None
+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
-def _launch_setup(program, cwd, args, env, stopAtBeginningOfMainSubprogram):
+# 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)
+def launch(
+ *,
+ program: Optional[str] = None,
+ cwd: Optional[str] = None,
+ args: Sequence[str] = (),
+ env: Optional[Mapping[str, str]] = None,
+ stopAtBeginningOfMainSubprogram: bool = False,
+ **extra,
+):
if cwd is not None:
exec_and_log("cd " + cwd)
if program is not None:
@@ -44,45 +50,33 @@ def _launch_setup(program, cwd, args, env, stopAtBeginningOfMainSubprogram):
inf.clear_env()
for name, value in env.items():
inf.set_env(name, value)
+ expect_process("process")
+ exec_and_expect_stop("run")
-# 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)
-def launch(
+@request("attach")
+def attach(
*,
program: Optional[str] = None,
- cwd: Optional[str] = None,
- args: Sequence[str] = (),
- env: Optional[Mapping[str, str]] = None,
- stopAtBeginningOfMainSubprogram: bool = False,
- **extra,
+ pid: Optional[int] = None,
+ target: Optional[str] = None,
+ **args,
):
- global _program
- _program = program
- _launch_setup(program, cwd, args, env, stopAtBeginningOfMainSubprogram)
-
-
-@request("attach")
-def attach(*, pid: Optional[int] = None, target: Optional[str] = None, **args):
- # Ensure configurationDone does not try to run.
- global _program
- _program = None
+ if program is not None:
+ exec_and_log("file " + program)
if pid is not None:
cmd = "attach " + str(pid)
elif target is not None:
cmd = "target remote " + target
else:
- raise Exception("attach requires either 'pid' or 'target'")
+ raise DAPException("attach requires either 'pid' or 'target'")
+ expect_process("attach")
+ expect_stop("attach")
exec_and_log(cmd)
@capability("supportsConfigurationDoneRequest")
-@request("configurationDone", response=False)
+@request("configurationDone")
def config_done(**args):
- global _program
- if _program is not None:
- # Suppress the continue event, but don't set any particular
- # expected stop.
- exec_and_expect_stop("run", None)
+ # Nothing to do.
+ return None
diff --git a/mingw32/share/gdb/python/gdb/dap/locations.py b/mingw32/share/gdb/python/gdb/dap/locations.py
index 032174df9c8..92e68f5e235 100644
--- a/mingw32/share/gdb/python/gdb/dap/locations.py
+++ b/mingw32/share/gdb/python/gdb/dap/locations.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Free Software Foundation, Inc.
+# Copyright 2023-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
@@ -13,25 +13,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
-
# This is deprecated in 3.9, but required in older versions.
from typing import Optional
+import gdb
+
from .server import capability, request
from .sources import decode_source
-from .startup import in_gdb_thread
-
-
-@in_gdb_thread
-def _find_lines(source, start_line, end_line):
- filename = decode_source(source)
- lines = set()
- for entry in gdb.execute_mi("-symbol-list-lines", filename)["lines"]:
- line = entry["line"]
- if line >= start_line and line <= end_line:
- lines.add(line)
- return {"breakpoints": [{"line": x} for x in sorted(lines)]}
# Note that the spec says that the arguments to this are optional.
@@ -46,4 +34,10 @@ def _find_lines(source, start_line, end_line):
def breakpoint_locations(*, source, line: int, endLine: Optional[int] = None, **extra):
if endLine is None:
endLine = line
- return _find_lines(source, line, endLine)
+ filename = decode_source(source)
+ lines = set()
+ for entry in gdb.execute_mi("-symbol-list-lines", filename)["lines"]:
+ this_line = entry["line"]
+ if this_line >= line and this_line <= endLine:
+ lines.add(this_line)
+ return {"breakpoints": [{"line": x} for x in sorted(lines)]}
diff --git a/mingw32/share/gdb/python/gdb/dap/memory.py b/mingw32/share/gdb/python/gdb/dap/memory.py
index 6b94f413045..4aa499633ec 100644
--- a/mingw32/share/gdb/python/gdb/dap/memory.py
+++ b/mingw32/share/gdb/python/gdb/dap/memory.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Free Software Foundation, Inc.
+# Copyright 2023-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
@@ -14,16 +14,21 @@
# along with this program. If not, see .
import base64
+
import gdb
-from .server import request, capability
+from .server import capability, request
+from .startup import DAPException
@request("readMemory")
@capability("supportsReadMemoryRequest")
def read_memory(*, memoryReference: str, offset: int = 0, count: int, **extra):
addr = int(memoryReference, 0) + offset
- buf = gdb.selected_inferior().read_memory(addr, count)
+ try:
+ buf = gdb.selected_inferior().read_memory(addr, count)
+ except MemoryError as e:
+ raise DAPException("Out of memory") from e
return {
"address": hex(addr),
"data": base64.b64encode(buf).decode("ASCII"),
diff --git a/mingw32/share/gdb/python/gdb/dap/modules.py b/mingw32/share/gdb/python/gdb/dap/modules.py
index 87a4f6be669..69e5a40d55a 100644
--- a/mingw32/share/gdb/python/gdb/dap/modules.py
+++ b/mingw32/share/gdb/python/gdb/dap/modules.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Free Software Foundation, Inc.
+# Copyright 2023-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
@@ -45,22 +45,17 @@ def make_module(objf):
return result
-@in_gdb_thread
-def _modules(start, count):
+@capability("supportsModulesRequest")
+@request("modules")
+def modules(*, startModule: int = 0, moduleCount: int = 0, **args):
# Don't count invalid objfiles or separate debug objfiles.
objfiles = [x for x in gdb.objfiles() if is_module(x)]
- if count == 0:
+ if moduleCount == 0:
# Use all items.
last = len(objfiles)
else:
- last = start + count
+ last = startModule + moduleCount
return {
- "modules": [make_module(x) for x in objfiles[start:last]],
+ "modules": [make_module(x) for x in objfiles[startModule:last]],
"totalModules": len(objfiles),
}
-
-
-@capability("supportsModulesRequest")
-@request("modules")
-def modules(*, startModule: int = 0, moduleCount: int = 0, **args):
- return _modules(startModule, moduleCount)
diff --git a/mingw32/share/gdb/python/gdb/dap/next.py b/mingw32/share/gdb/python/gdb/dap/next.py
index eedc26f28a5..1dc1d6dd74d 100644
--- a/mingw32/share/gdb/python/gdb/dap/next.py
+++ b/mingw32/share/gdb/python/gdb/dap/next.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -15,9 +15,9 @@
import gdb
-from .events import StopKinds, exec_and_expect_stop
-from .server import capability, request
-from .startup import in_gdb_thread, send_gdb, send_gdb_with_response
+from .events import exec_and_expect_stop
+from .server import capability, request, send_gdb, send_gdb_with_response
+from .startup import in_gdb_thread
from .state import set_thread
@@ -57,7 +57,7 @@ def next(
cmd = "next"
if granularity == "instruction":
cmd += "i"
- exec_and_expect_stop(cmd, StopKinds.STEP)
+ exec_and_expect_stop(cmd)
@capability("supportsSteppingGranularity")
@@ -70,13 +70,13 @@ def step_in(
cmd = "step"
if granularity == "instruction":
cmd += "i"
- exec_and_expect_stop(cmd, StopKinds.STEP)
+ exec_and_expect_stop(cmd)
@request("stepOut", response=False)
def step_out(*, threadId: int, singleThread: bool = False, **args):
_handle_thread_step(threadId, singleThread, True)
- exec_and_expect_stop("finish", StopKinds.STEP)
+ exec_and_expect_stop("finish")
# This is a server-side request because it is funny: it wants to
@@ -87,5 +87,5 @@ def step_out(*, threadId: int, singleThread: bool = False, **args):
@request("continue", on_dap_thread=True)
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", None))
+ send_gdb(lambda: exec_and_expect_stop("continue"))
return {"allThreadsContinued": not locked}
diff --git a/mingw32/share/gdb/python/gdb/dap/pause.py b/mingw32/share/gdb/python/gdb/dap/pause.py
index b7e21452d69..d874a607a81 100644
--- a/mingw32/share/gdb/python/gdb/dap/pause.py
+++ b/mingw32/share/gdb/python/gdb/dap/pause.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,10 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from .events import StopKinds, exec_and_expect_stop
+from .events import exec_and_expect_stop
from .server import request
@request("pause", response=False, expect_stopped=False)
def pause(**args):
- exec_and_expect_stop("interrupt -a", StopKinds.PAUSE)
+ exec_and_expect_stop("interrupt -a", True)
diff --git a/mingw32/share/gdb/python/gdb/dap/scopes.py b/mingw32/share/gdb/python/gdb/dap/scopes.py
index 63cd3255b8a..8cd860141d6 100644
--- a/mingw32/share/gdb/python/gdb/dap/scopes.py
+++ b/mingw32/share/gdb/python/gdb/dap/scopes.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -16,26 +16,40 @@
import gdb
from .frames import frame_for_id
-from .startup import in_gdb_thread
from .server import request
+from .startup import in_gdb_thread
from .varref import BaseReference
-
# Map DAP frame IDs to scopes. This ensures that scopes are re-used.
frame_to_scope = {}
+# If the most recent stop was due to a 'finish', and the return value
+# could be computed, then this holds that value. Otherwise it holds
+# None.
+_last_return_value = None
+
+
# When the inferior is re-started, we erase all scope references. See
# the section "Lifetime of Objects References" in the spec.
@in_gdb_thread
def clear_scopes(event):
global frame_to_scope
frame_to_scope = {}
+ global _last_return_value
+ _last_return_value = None
gdb.events.cont.connect(clear_scopes)
+@in_gdb_thread
+def set_finish_value(val):
+ """Set the current 'finish' value on a stop."""
+ global _last_return_value
+ _last_return_value = val
+
+
# A helper function to compute the value of a symbol. SYM is either a
# gdb.Symbol, or an object implementing the SymValueWrapper interface.
# FRAME is a frame wrapper, as produced by a frame filter. Returns a
@@ -76,7 +90,7 @@ def to_object(self):
result["presentationHint"] = self.hint
# How would we know?
result["expensive"] = False
- result["namedVariables"] = len(self.var_list)
+ result["namedVariables"] = self.child_count()
if self.line is not None:
result["line"] = self.line
# FIXME construct a Source object
@@ -93,6 +107,22 @@ def fetch_one_child(self, idx):
return symbol_value(self.var_list[idx], self.frame)
+# A _ScopeReference that prepends the most recent return value. Note
+# that this object is only created if such a value actually exists.
+class _FinishScopeReference(_ScopeReference):
+ def __init__(self, *args):
+ super().__init__(*args)
+
+ def child_count(self):
+ return super().child_count() + 1
+
+ def fetch_one_child(self, idx):
+ if idx == 0:
+ global _last_return_value
+ return ("(return)", _last_return_value)
+ return super().fetch_one_child(idx - 1)
+
+
class _RegisterReference(_ScopeReference):
def __init__(self, name, frame):
super().__init__(
@@ -107,30 +137,28 @@ def fetch_one_child(self, idx):
)
-# Helper function to create a DAP scopes for a given frame ID.
-@in_gdb_thread
-def _get_scope(id):
+@request("scopes")
+def scopes(*, frameId: int, **extra):
+ global _last_return_value
global frame_to_scope
- if id in frame_to_scope:
- scopes = frame_to_scope[id]
+ if frameId in frame_to_scope:
+ scopes = frame_to_scope[frameId]
else:
- frame = frame_for_id(id)
+ frame = frame_for_id(frameId)
scopes = []
# Make sure to handle the None case as well as the empty
# iterator case.
args = tuple(frame.frame_args() or ())
if args:
scopes.append(_ScopeReference("Arguments", "arguments", frame, 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:
+ if has_return_value:
+ scopes.append(_FinishScopeReference("Locals", "locals", frame, locs))
+ elif locs:
scopes.append(_ScopeReference("Locals", "locals", frame, locs))
scopes.append(_RegisterReference("Registers", frame))
- frame_to_scope[id] = scopes
- return [x.to_object() for x in scopes]
-
-
-@request("scopes")
-def scopes(*, frameId: int, **extra):
- return {"scopes": _get_scope(frameId)}
+ frame_to_scope[frameId] = scopes
+ return {"scopes": [x.to_object() for x in scopes]}
diff --git a/mingw32/share/gdb/python/gdb/dap/server.py b/mingw32/share/gdb/python/gdb/dap/server.py
index 031bf49f486..7eb87177710 100644
--- a/mingw32/share/gdb/python/gdb/dap/server.py
+++ b/mingw32/share/gdb/python/gdb/dap/server.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -14,25 +14,29 @@
# along with this program. If not, see .
import functools
+import heapq
import inspect
import json
-import queue
-import sys
+import threading
+from contextlib import contextmanager
-from .io import start_json_writer, read_json
+import gdb
+
+from .io import read_json, start_json_writer
from .startup import (
+ DAPException,
+ DAPQueue,
+ LogLevel,
exec_and_log,
in_dap_thread,
in_gdb_thread,
- send_gdb,
- send_gdb_with_response,
- start_thread,
log,
log_stack,
+ start_thread,
+ thread_log,
)
from .typecheck import type_check
-
# Map capability names to values.
_capabilities = {}
@@ -43,6 +47,76 @@
_server = None
+# A subclass of Exception that is used solely for reporting that a
+# request needs the inferior to be stopped, but it is not stopped.
+class NotStoppedException(Exception):
+ pass
+
+
+# This is used to handle cancellation requests. It tracks all the
+# needed state, so that we can cancel both requests that are in flight
+# as well as queued requests.
+class CancellationHandler:
+ def __init__(self):
+ # Methods on this class acquire this lock before proceeding.
+ self.lock = threading.Lock()
+ # The request currently being handled, or None.
+ self.in_flight_dap_thread = None
+ self.in_flight_gdb_thread = None
+ self.reqs = []
+
+ def starting(self, req):
+ """Call at the start of the given request."""
+ with self.lock:
+ self.in_flight_dap_thread = req
+
+ def done(self, req):
+ """Indicate that the request is done."""
+ with self.lock:
+ self.in_flight_dap_thread = None
+
+ def cancel(self, req):
+ """Call to cancel a request.
+
+ If the request has already finished, this is ignored.
+ If the request is in flight, it is interrupted.
+ If the request has not yet been seen, the cancellation is queued."""
+ with self.lock:
+ if req == self.in_flight_gdb_thread:
+ gdb.interrupt()
+ else:
+ # We don't actually ignore the request here, but in
+ # the 'starting' 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)
+
+ @contextmanager
+ def interruptable_region(self, req):
+ """Return a new context manager that sets in_flight_gdb_thread to
+ REQ."""
+ if req is None:
+ # No request is handled in the region, just execute the region.
+ yield
+ 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()
+ # Request is being handled by the gdb thread.
+ self.in_flight_gdb_thread = req
+ # Execute region. This may be interrupted by gdb.interrupt.
+ yield
+ finally:
+ with self.lock:
+ # Request is no longer handled by the gdb thread.
+ self.in_flight_gdb_thread = None
+
+
class Server:
"""The DAP server class."""
@@ -54,11 +128,12 @@ def __init__(self, in_stream, out_stream, child_stream):
# 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.
- if sys.version_info[0] == 3 and sys.version_info[1] <= 6:
- self.write_queue = queue.Queue()
- else:
- self.write_queue = queue.SimpleQueue()
+ 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.canceller = CancellationHandler()
global _server
_server = self
@@ -66,13 +141,14 @@ def __init__(self, in_stream, out_stream, child_stream):
# PARAMS is just a dictionary from the JSON.
@in_dap_thread
def _handle_command(self, params):
- # We don't handle 'cancel' for now.
+ req = params["seq"]
result = {
- "request_seq": params["seq"],
+ "request_seq": req,
"type": "response",
"command": params["command"],
}
try:
+ self.canceller.starting(req)
if "arguments" in params:
args = params["arguments"]
else:
@@ -82,10 +158,26 @@ def _handle_command(self, params):
if body is not None:
result["body"] = body
result["success"] = True
+ except NotStoppedException:
+ # This is an expected exception, and the result is clearly
+ # visible in the log, so do not log it.
+ result["success"] = False
+ result["message"] = "notStopped"
+ except KeyboardInterrupt:
+ # This can only happen when a request has been canceled.
+ result["success"] = False
+ result["message"] = "cancelled"
+ except DAPException as e:
+ # Don't normally want to see this, as it interferes with
+ # the test suite.
+ log_stack(LogLevel.FULL)
+ result["success"] = False
+ result["message"] = str(e)
except BaseException as e:
log_stack()
result["success"] = False
result["message"] = str(e)
+ self.canceller.done(req)
return result
# Read inferior output and sends OutputEvents to the client. It
@@ -106,16 +198,45 @@ def _send_json(self, obj):
log("WROTE: <<<" + json.dumps(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
+ # used so that 'cancel' requests can be handled -- the DAP thread
+ # will normally block, waiting for each request to complete.
+ def _reader_thread(self):
+ while True:
+ cmd = read_json(self.in_stream)
+ if cmd is None:
+ break
+ log("READ: <<<" + json.dumps(cmd) + ">>>")
+ # Be extra paranoid about the form here. If anything is
+ # missing, it will be put in the queue and then an error
+ # issued by ordinary request processing.
+ if (
+ "command" in cmd
+ and cmd["command"] == "cancel"
+ and "arguments" in cmd
+ # gdb does not implement progress, so there's no need
+ # to check for progressId.
+ and "requestId" in cmd["arguments"]
+ ):
+ self.canceller.cancel(cmd["arguments"]["requestId"])
+ self.read_queue.put(cmd)
+ # When we hit EOF, signal it with None.
+ self.read_queue.put(None)
+
@in_dap_thread
def main_loop(self):
"""The main loop of the DAP 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)
- 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 = read_json(self.in_stream)
- log("READ: <<<" + json.dumps(cmd) + ">>>")
+ 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)
events = self.delayed_events
@@ -126,6 +247,8 @@ def main_loop(self):
# JSON-writing thread, so that we can ensure that all
# responses are flushed to the client before exiting.
self.write_queue.put(None)
+ json_writer.join()
+ send_gdb("quit")
@in_dap_thread
def send_event_later(self, event, body=None):
@@ -173,7 +296,7 @@ def check(*args, **kwargs):
from .events import inferior_running
if inferior_running:
- raise Exception("notStopped")
+ raise NotStoppedException()
return func(*args, **kwargs)
return check
@@ -251,6 +374,7 @@ def non_sync_call(**args):
cmd = _check_not_running(cmd)
global _commands
+ assert name not in _commands
_commands[name] = cmd
return cmd
@@ -263,6 +387,7 @@ def capability(name, value=True):
def wrap(func):
global _capabilities
+ assert name not in _capabilities
_capabilities[name] = value
return func
@@ -300,3 +425,91 @@ def disconnect(*, terminateDebuggee: bool = False, **args):
if terminateDebuggee:
send_gdb_with_response("kill")
_server.shutdown()
+
+
+@request("cancel", on_dap_thread=True, expect_stopped=False)
+@capability("supportsCancelRequest")
+def cancel(**args):
+ # If a 'cancel' request can actually be satisfied, it will be
+ # handled specially in the reader thread. However, in order to
+ # construct a proper response, the request is also added to the
+ # command queue and so ends up here. Additionally, the spec says:
+ # The cancel request may return an error if it could not cancel
+ # an operation but a client should refrain from presenting this
+ # error to end users.
+ # ... which gdb takes to mean that it is fine for all cancel
+ # requests to report success.
+ return None
+
+
+class Invoker(object):
+ """A simple class that can invoke a gdb command."""
+
+ def __init__(self, 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)
+
+
+class Cancellable(object):
+
+ def __init__(self, fn, result_q=None):
+ self.fn = fn
+ self.result_q = result_q
+ with _server.canceller.lock:
+ self.req = _server.canceller.in_flight_dap_thread
+
+ # This is invoked in the gdb thread to run self.fn.
+ @in_gdb_thread
+ 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)
+ except (Exception, KeyboardInterrupt) as e:
+ if self.result_q is not None:
+ # Pass result or exception to caller.
+ self.result_q.put(e)
+ elif isinstance(e, KeyboardInterrupt):
+ # Fn was cancelled.
+ pass
+ else:
+ # Exception happened. Ignore and log it.
+ err_string = "%s, %s" % (e, type(e))
+ thread_log("caught exception: " + err_string)
+ log_stack()
+
+
+def send_gdb(cmd):
+ """Send CMD to the gdb thread.
+ CMD can be either a function or a string.
+ If it is a string, it is passed to gdb.execute."""
+ if isinstance(cmd, str):
+ cmd = Invoker(cmd)
+
+ # Post the event and don't wait for the result.
+ gdb.post_event(Cancellable(cmd))
+
+
+def send_gdb_with_response(fn):
+ """Send FN to the gdb thread and return its result.
+ If FN is a string, it is passed to gdb.execute and None is
+ returned as the result.
+ If FN throws an exception, this function will throw the
+ same exception in the calling thread.
+ """
+ if isinstance(fn, str):
+ fn = Invoker(fn)
+
+ # Post the event and wait for the result in result_q.
+ result_q = DAPQueue()
+ gdb.post_event(Cancellable(fn, result_q))
+ val = result_q.get()
+
+ if isinstance(val, (Exception, KeyboardInterrupt)):
+ raise val
+ return val
diff --git a/mingw32/share/gdb/python/gdb/dap/sources.py b/mingw32/share/gdb/python/gdb/dap/sources.py
index 821205cedd1..ad0c913c8c1 100644
--- a/mingw32/share/gdb/python/gdb/dap/sources.py
+++ b/mingw32/share/gdb/python/gdb/dap/sources.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Free Software Foundation, Inc.
+# Copyright 2023-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
@@ -17,9 +17,8 @@
import gdb
-from .server import request, capability
-from .startup import in_gdb_thread
-
+from .server import capability, request
+from .startup import DAPException, in_gdb_thread
# The next available source reference ID. Must be greater than 0.
_next_source = 1
@@ -33,16 +32,20 @@
@in_gdb_thread
-def make_source(fullname, filename):
+def make_source(fullname, filename=None):
"""Return the Source for a given file name.
FULLNAME is the full name. This is used as the key.
- FILENAME is the base name.
+ 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:
+ if filename is None:
+ filename = os.path.basename(fullname)
+
result = {
"name": filename,
"path": fullname,
@@ -68,11 +71,11 @@ def decode_source(source):
if "path" in source:
return source["path"]
if "sourceReference" not in source:
- raise Exception("either 'path' or 'sourceReference' must appear in Source")
+ raise DAPException("either 'path' or 'sourceReference' must appear in Source")
ref = source["sourceReference"]
global _id_map
if ref not in _id_map:
- raise Exception("no sourceReference " + str(ref))
+ raise DAPException("no sourceReference " + str(ref))
return _id_map[ref]["path"]
diff --git a/mingw32/share/gdb/python/gdb/dap/startup.py b/mingw32/share/gdb/python/gdb/dap/startup.py
index eba072147ee..58591c00b97 100644
--- a/mingw32/share/gdb/python/gdb/dap/startup.py
+++ b/mingw32/share/gdb/python/gdb/dap/startup.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -16,11 +16,20 @@
# Do not import other gdbdap modules here -- this module must come
# first.
import functools
-import gdb
import queue
+import sys
import threading
import traceback
-import sys
+from enum import IntEnum, auto
+
+import gdb
+
+# Adapt to different Queue types. This is exported for use in other
+# modules as well.
+if sys.version_info[0] == 3 and sys.version_info[1] <= 6:
+ DAPQueue = queue.Queue
+else:
+ DAPQueue = queue.SimpleQueue
# The GDB thread, aka the main thread.
@@ -31,12 +40,46 @@
_dap_thread = None
+# "Known" exceptions are wrapped in a DAP exception, so that, by
+# default, only rogue exceptions are logged -- this is then used by
+# the test suite.
+class DAPException(Exception):
+ pass
+
+
+# Wrapper for gdb.parse_and_eval that turns exceptions into
+# DAPException.
+def parse_and_eval(expression, global_context=False):
+ try:
+ return gdb.parse_and_eval(expression, global_context=global_context)
+ except Exception as e:
+ # Be sure to preserve the summary, as this can propagate to
+ # the client.
+ raise DAPException(str(e)) from e
+
+
def start_thread(name, target, args=()):
"""Start a new thread, invoking TARGET with *ARGS there.
This is a helper function that ensures that any GDB signals are
correctly blocked."""
- result = gdb.Thread(name=name, target=target, args=args, daemon=True)
+
+ def thread_wrapper(*args):
+ # Catch any exception, and log it. If we let it escape here, it'll be
+ # printed in gdb_stderr, which is not safe to access from anywhere but
+ # gdb's main thread.
+ try:
+ target(*args)
+ except Exception as err:
+ err_string = "%s, %s" % (err, type(err))
+ thread_log("caught exception: " + err_string)
+ log_stack()
+ finally:
+ # Log when a thread terminates.
+ thread_log("terminating")
+
+ result = gdb.Thread(name=name, target=thread_wrapper, args=args, daemon=True)
result.start()
+ return result
def start_dap(target):
@@ -50,7 +93,15 @@ def really_start_dap():
_dap_thread = threading.current_thread()
target()
- start_thread("DAP", really_start_dap)
+ # Note: unlike _dap_thread, dap_thread is a local variable.
+ dap_thread = start_thread("DAP", really_start_dap)
+
+ def _on_gdb_exiting(event):
+ thread_log("joining DAP thread ...")
+ dap_thread.join()
+ thread_log("joining DAP thread done")
+
+ gdb.events.gdb_exiting.connect(_on_gdb_exiting)
def in_gdb_thread(func):
@@ -75,12 +126,35 @@ def ensure_dap_thread(*args, **kwargs):
return ensure_dap_thread
+# Logging levels.
+class LogLevel(IntEnum):
+ DEFAULT = auto()
+ FULL = auto()
+
+
+class LogLevelParam(gdb.Parameter):
+ """DAP logging level."""
+
+ set_doc = "Set the DAP logging level."
+ show_doc = "Show the DAP logging level."
+
+ def __init__(self):
+ super().__init__(
+ "debug dap-log-level", gdb.COMMAND_MAINTENANCE, gdb.PARAM_ZUINTEGER
+ )
+ self.value = LogLevel.DEFAULT
+
+
+_log_level = LogLevelParam()
+
+
class LoggingParam(gdb.Parameter):
"""Whether DAP logging is enabled."""
set_doc = "Set the DAP logging status."
show_doc = "Show the DAP logging status."
+ lock = threading.Lock()
log_file = None
def __init__(self):
@@ -90,29 +164,43 @@ def __init__(self):
self.value = None
def get_set_string(self):
- # Close any existing log file, no matter what.
- if self.log_file is not None:
- self.log_file.close()
- self.log_file = None
- if self.value is not None:
- self.log_file = open(self.value, "w")
+ with dap_log.lock:
+ # Close any existing log file, no matter what.
+ if self.log_file is not None:
+ self.log_file.close()
+ self.log_file = None
+ if self.value is not None:
+ self.log_file = open(self.value, "w")
return ""
dap_log = LoggingParam()
-def log(something):
+def log(something, level=LogLevel.DEFAULT):
"""Log SOMETHING to the log file, if logging is enabled."""
- if dap_log.log_file is not None:
- print(something, file=dap_log.log_file)
- dap_log.log_file.flush()
+ with dap_log.lock:
+ if dap_log.log_file is not None and level <= _log_level.value:
+ print(something, file=dap_log.log_file)
+ dap_log.log_file.flush()
+
+def thread_log(something, level=LogLevel.DEFAULT):
+ """Log SOMETHING to the log file, if logging is enabled, and prefix
+ the thread name."""
+ if threading.current_thread() is _gdb_thread:
+ thread_name = "GDB main"
+ else:
+ thread_name = threading.current_thread().name
+ log(thread_name + ": " + something, level)
-def log_stack():
+
+def log_stack(level=LogLevel.DEFAULT):
"""Log a stack trace to the log file, if logging is enabled."""
- if dap_log.log_file is not None:
- traceback.print_exc(file=dap_log.log_file)
+ with dap_log.lock:
+ if dap_log.log_file is not None and level <= _log_level.value:
+ traceback.print_exc(file=dap_log.log_file)
+ dap_log.log_file.flush()
@in_gdb_thread
@@ -126,52 +214,3 @@ def exec_and_log(cmd):
log(">>> " + output)
except gdb.error:
log_stack()
-
-
-class Invoker(object):
- """A simple class that can invoke a gdb command."""
-
- def __init__(self, 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)
-
-
-def send_gdb(cmd):
- """Send CMD to the gdb thread.
- CMD can be either a function or a string.
- If it is a string, it is passed to gdb.execute."""
- if isinstance(cmd, str):
- cmd = Invoker(cmd)
- gdb.post_event(cmd)
-
-
-def send_gdb_with_response(fn):
- """Send FN to the gdb thread and return its result.
- If FN is a string, it is passed to gdb.execute and None is
- returned as the result.
- If FN throws an exception, this function will throw the
- same exception in the calling thread.
- """
- if isinstance(fn, str):
- fn = Invoker(fn)
- if sys.version_info[0] == 3 and sys.version_info[1] <= 6:
- result_q = queue.Queue()
- else:
- result_q = queue.SimpleQueue()
-
- def message():
- try:
- val = fn()
- result_q.put(val)
- except Exception as e:
- result_q.put(e)
-
- send_gdb(message)
- val = result_q.get()
- if isinstance(val, Exception):
- raise val
- return val
diff --git a/mingw32/share/gdb/python/gdb/dap/state.py b/mingw32/share/gdb/python/gdb/dap/state.py
index f4f81d2afa0..57ae355f45e 100644
--- a/mingw32/share/gdb/python/gdb/dap/state.py
+++ b/mingw32/share/gdb/python/gdb/dap/state.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from .startup import in_gdb_thread, exec_and_log, log
+from .startup import exec_and_log, in_gdb_thread, log
@in_gdb_thread
diff --git a/mingw32/share/gdb/python/gdb/dap/threads.py b/mingw32/share/gdb/python/gdb/dap/threads.py
index 515966761ce..e65495b41db 100644
--- a/mingw32/share/gdb/python/gdb/dap/threads.py
+++ b/mingw32/share/gdb/python/gdb/dap/threads.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2022-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
diff --git a/mingw32/share/gdb/python/gdb/dap/typecheck.py b/mingw32/share/gdb/python/gdb/dap/typecheck.py
index 791dc75f7b3..55896cccb06 100644
--- a/mingw32/share/gdb/python/gdb/dap/typecheck.py
+++ b/mingw32/share/gdb/python/gdb/dap/typecheck.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Free Software Foundation, Inc.
+# Copyright 2023-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
diff --git a/mingw32/share/gdb/python/gdb/dap/varref.py b/mingw32/share/gdb/python/gdb/dap/varref.py
index 8f0a070498c..57e84a1676e 100644
--- a/mingw32/share/gdb/python/gdb/dap/varref.py
+++ b/mingw32/share/gdb/python/gdb/dap/varref.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Free Software Foundation, Inc.
+# Copyright 2023-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
@@ -13,13 +13,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
-from .startup import in_gdb_thread
-from .server import client_bool_capability
from abc import ABC, abstractmethod
from collections import defaultdict
from contextlib import contextmanager
+import gdb
+
+from .server import client_bool_capability
+from .startup import DAPException, in_gdb_thread
# A list of all the variable references created during this pause.
all_variables = []
@@ -165,7 +166,7 @@ def find_child_by_name(self, name):
# map here.
if name in self.by_name:
return self.by_name[name]
- raise Exception("no variable named '" + name + "'")
+ raise DAPException("no variable named '" + name + "'")
class VariableReference(BaseReference):
@@ -271,5 +272,5 @@ def find_variable(ref):
# Variable references are offset by 1.
ref = ref - 1
if ref < 0 or ref > len(all_variables):
- raise Exception("invalid variablesReference")
+ raise DAPException("invalid variablesReference")
return all_variables[ref]
diff --git a/mingw32/share/gdb/python/gdb/disassembler.py b/mingw32/share/gdb/python/gdb/disassembler.py
index 88f65f5b2b6..72d311b117f 100644
--- a/mingw32/share/gdb/python/gdb/disassembler.py
+++ b/mingw32/share/gdb/python/gdb/disassembler.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021-2023 Free Software Foundation, Inc.
+# Copyright (C) 2021-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
@@ -15,12 +15,14 @@
"""Disassembler related module."""
-import gdb
import _gdb.disassembler
# Re-export everything from the _gdb.disassembler module, which is
-# defined within GDB's C++ code.
-from _gdb.disassembler import *
+# defined within GDB's C++ code. Note that two indicators are needed
+# here to silence flake8.
+from _gdb.disassembler import * # noqa: F401,F403
+
+import gdb
# Module global dictionary of gdb.disassembler.Disassembler objects.
# The keys of this dictionary are bfd architecture names, or the
@@ -93,21 +95,14 @@ def _print_insn(info):
disassembled."""
def lookup_disassembler(arch):
- try:
- name = arch.name()
- if name is None:
- return None
- if name in _disassemblers_dict:
- return _disassemblers_dict[name]
- if None in _disassemblers_dict:
- return _disassemblers_dict[None]
- return None
- except:
- # It's pretty unlikely this exception case will ever
- # trigger, one situation would be if the user somehow
- # corrupted the _disassemblers_dict variable such that it
- # was no longer a dictionary.
+ name = arch.name()
+ if name is None:
return None
+ if name in _disassemblers_dict:
+ return _disassemblers_dict[name]
+ if None in _disassemblers_dict:
+ return _disassemblers_dict[None]
+ return None
disassembler = lookup_disassembler(info.architecture)
if disassembler is None:
diff --git a/mingw32/share/gdb/python/gdb/frames.py b/mingw32/share/gdb/python/gdb/frames.py
index 833941e8079..a3be80c72a0 100644
--- a/mingw32/share/gdb/python/gdb/frames.py
+++ b/mingw32/share/gdb/python/gdb/frames.py
@@ -1,5 +1,5 @@
# Frame-filter commands.
-# Copyright (C) 2013-2023 Free Software Foundation, Inc.
+# 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
@@ -16,11 +16,12 @@
"""Internal functions for working with frame-filters."""
+import collections
+import itertools
+
import gdb
+from gdb.FrameDecorator import DAPFrameDecorator, FrameDecorator
from gdb.FrameIterator import FrameIterator
-from gdb.FrameDecorator import FrameDecorator, DAPFrameDecorator
-import itertools
-import collections
def get_priority(filter_item):
diff --git a/mingw32/share/gdb/python/gdb/function/__init__.py b/mingw32/share/gdb/python/gdb/function/__init__.py
index 34496c1ebd6..4b64bc31b5b 100644
--- a/mingw32/share/gdb/python/gdb/function/__init__.py
+++ b/mingw32/share/gdb/python/gdb/function/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 Free Software Foundation, Inc.
+# Copyright (C) 2012-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
diff --git a/mingw32/share/gdb/python/gdb/function/as_string.py b/mingw32/share/gdb/python/gdb/function/as_string.py
index 4bd6579ca64..a255fff6402 100644
--- a/mingw32/share/gdb/python/gdb/function/as_string.py
+++ b/mingw32/share/gdb/python/gdb/function/as_string.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 Free Software Foundation, Inc.
+# Copyright (C) 2016-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
diff --git a/mingw32/share/gdb/python/gdb/function/caller_is.py b/mingw32/share/gdb/python/gdb/function/caller_is.py
index 220b2227492..bacd8c0ef8b 100644
--- a/mingw32/share/gdb/python/gdb/function/caller_is.py
+++ b/mingw32/share/gdb/python/gdb/function/caller_is.py
@@ -1,5 +1,5 @@
# Caller-is functions.
-# Copyright (C) 2008-2023 Free Software Foundation, Inc.
+# Copyright (C) 2008-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
@@ -14,9 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import gdb
import re
+import gdb
+
class CallerIs(gdb.Function):
"""Check the calling function's name.
diff --git a/mingw32/share/gdb/python/gdb/function/strfns.py b/mingw32/share/gdb/python/gdb/function/strfns.py
index 7dc464b7f54..90c9ceab867 100644
--- a/mingw32/share/gdb/python/gdb/function/strfns.py
+++ b/mingw32/share/gdb/python/gdb/function/strfns.py
@@ -1,5 +1,5 @@
# Useful gdb string convenience functions.
-# Copyright (C) 2012-2023 Free Software Foundation, Inc.
+# Copyright (C) 2012-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
@@ -16,9 +16,10 @@
"""$_memeq, $_strlen, $_streq, $_regex"""
-import gdb
import re
+import gdb
+
class _MemEq(gdb.Function):
"""$_memeq - compare bytes of memory.
diff --git a/mingw32/share/gdb/python/gdb/missing_debug.py b/mingw32/share/gdb/python/gdb/missing_debug.py
new file mode 100644
index 00000000000..6d57462c185
--- /dev/null
+++ b/mingw32/share/gdb/python/gdb/missing_debug.py
@@ -0,0 +1,185 @@
+# Copyright (C) 2023-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 .
+
+"""
+MissingDebugHandler base class, and register_handler function.
+"""
+
+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:
+ # Fall back to curses.ascii.isascii() and curses.ascii.isalnum() for
+ # earlier versions.
+ from curses.ascii import isalnum, isascii
+
+
+def _validate_name(name):
+ """Validate a missing debug handler name string.
+
+ If name is valid as a missing debug handler name, then this
+ function does nothing. If name is not valid then an exception is
+ raised.
+
+ Arguments:
+ name: A string, the name of a missing debug handler.
+
+ Returns:
+ Nothing.
+
+ Raises:
+ ValueError: If name is invalid as a missing debug handler
+ name.
+ """
+ for ch in name:
+ if not isascii(ch) or not (isalnum(ch) or ch in "_-"):
+ raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
+
+
+class MissingDebugHandler(object):
+ """Base class for missing debug handlers written in Python.
+
+ A missing debug 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 __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.
+
+ Arguments:
+ objfile: A gdb.Objfile for which GDB could not find any
+ debug information.
+
+ Returns:
+ True: GDB should try again to locate the debug information
+ for objfile, the handler may have installed the
+ missing information.
+ False: GDB should move on without the debug information
+ for objfile.
+ A string: GDB should load the file at the given path; it
+ contains the debug information for objfile.
+ None: This handler can't help with objfile. GDB should
+ try any other registered handlers.
+ """
+ raise NotImplementedError("MissingDebugHandler.__call__()")
+
+
+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)
diff --git a/mingw32/share/gdb/python/gdb/printer/__init__.py b/mingw32/share/gdb/python/gdb/printer/__init__.py
index b25b7eaa9fc..6692044897b 100644
--- a/mingw32/share/gdb/python/gdb/printer/__init__.py
+++ b/mingw32/share/gdb/python/gdb/printer/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 Free Software Foundation, Inc.
+# Copyright (C) 2014-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
diff --git a/mingw32/share/gdb/python/gdb/printer/bound_registers.py b/mingw32/share/gdb/python/gdb/printer/bound_registers.py
index b5298b9ee28..d00b455ddb9 100644
--- a/mingw32/share/gdb/python/gdb/printer/bound_registers.py
+++ b/mingw32/share/gdb/python/gdb/printer/bound_registers.py
@@ -1,5 +1,5 @@
# Pretty-printers for bounds registers.
-# Copyright (C) 2013-2023 Free Software Foundation, Inc.
+# 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
diff --git a/mingw32/share/gdb/python/gdb/printing.py b/mingw32/share/gdb/python/gdb/printing.py
index 14bd84b5859..55ba43585ec 100644
--- a/mingw32/share/gdb/python/gdb/printing.py
+++ b/mingw32/share/gdb/python/gdb/printing.py
@@ -1,5 +1,5 @@
# Pretty-printer utilities.
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
@@ -16,11 +16,12 @@
"""Utilities for working with pretty-printers."""
-import gdb
-import gdb.types
import itertools
import re
+import gdb
+import gdb.types
+
class PrettyPrinter(object):
"""A basic pretty-printer.
diff --git a/mingw32/share/gdb/python/gdb/prompt.py b/mingw32/share/gdb/python/gdb/prompt.py
index 9bbfcb9a272..4ad38e4567a 100644
--- a/mingw32/share/gdb/python/gdb/prompt.py
+++ b/mingw32/share/gdb/python/gdb/prompt.py
@@ -1,5 +1,5 @@
# Extended prompt utilities.
-# Copyright (C) 2011-2023 Free Software Foundation, Inc.
+# Copyright (C) 2011-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
@@ -16,9 +16,10 @@
""" Extended prompt library functions."""
-import gdb
import os
+import gdb
+
def _prompt_pwd(ignore):
"The current working directory."
diff --git a/mingw32/share/gdb/python/gdb/styling.py b/mingw32/share/gdb/python/gdb/styling.py
index 8540ab23bec..1c5394e479b 100644
--- a/mingw32/share/gdb/python/gdb/styling.py
+++ b/mingw32/share/gdb/python/gdb/styling.py
@@ -1,5 +1,5 @@
# Styling related hooks.
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
@@ -19,9 +19,9 @@
import gdb
try:
- from pygments import formatters, lexers, highlight
- from pygments.token import Error, Comment, Text
+ from pygments import formatters, highlight, lexers
from pygments.filters import TokenMergeFilter
+ from pygments.token import Comment, Error, Text
_formatter = None
@@ -39,7 +39,7 @@ def colorize(filename, contents):
return highlight(contents, lexer, formatter).encode(
gdb.host_charset(), "backslashreplace"
)
- except:
+ except Exception:
return None
class HandleNasmComments(TokenMergeFilter):
@@ -70,7 +70,7 @@ def __get_asm_lexer(gdbarch):
flavor = gdb.parameter("disassembly-flavor")
if flavor == "intel" and gdbarch.name()[:4] == "i386":
lexer_type = "nasm"
- except:
+ except Exception:
# If GDB is built without i386 support then attempting to fetch
# the 'disassembly-flavor' parameter will throw an error, which we
# ignore.
@@ -89,10 +89,10 @@ def colorize_disasm(content, gdbarch):
lexer = __get_asm_lexer(gdbarch)
formatter = get_formatter()
return highlight(content, lexer, formatter).rstrip().encode()
- except:
+ except Exception:
return content
-except:
+except ImportError:
def colorize(filename, contents):
return None
diff --git a/mingw32/share/gdb/python/gdb/types.py b/mingw32/share/gdb/python/gdb/types.py
index dcec301ad4d..b4af59c105b 100644
--- a/mingw32/share/gdb/python/gdb/types.py
+++ b/mingw32/share/gdb/python/gdb/types.py
@@ -1,5 +1,5 @@
# Type utilities.
-# Copyright (C) 2010-2023 Free Software Foundation, Inc.
+# Copyright (C) 2010-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
diff --git a/mingw32/share/gdb/python/gdb/unwinder.py b/mingw32/share/gdb/python/gdb/unwinder.py
index 140b84d3374..bb0db79a198 100644
--- a/mingw32/share/gdb/python/gdb/unwinder.py
+++ b/mingw32/share/gdb/python/gdb/unwinder.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2023 Free Software Foundation, Inc.
+# Copyright (C) 2015-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
diff --git a/mingw32/share/gdb/python/gdb/xmethod.py b/mingw32/share/gdb/python/gdb/xmethod.py
index ef7d2011e1c..c98402d271f 100644
--- a/mingw32/share/gdb/python/gdb/xmethod.py
+++ b/mingw32/share/gdb/python/gdb/xmethod.py
@@ -1,5 +1,5 @@
# Python side of the support for xmethods.
-# Copyright (C) 2013-2023 Free Software Foundation, Inc.
+# 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
@@ -16,9 +16,10 @@
"""Utilities for defining xmethods"""
-import gdb
import re
+import gdb
+
class XMethod(object):
"""Base class (or a template) for an xmethod description.
diff --git a/mingw32/share/gdb/syscalls/aarch64-linux.xml b/mingw32/share/gdb/syscalls/aarch64-linux.xml
index 1daafaac814..14caea5ab07 100644
--- a/mingw32/share/gdb/syscalls/aarch64-linux.xml
+++ b/mingw32/share/gdb/syscalls/aarch64-linux.xml
@@ -1,6 +1,6 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mingw32/share/gdb/syscalls/mips-n32-linux.xml b/mingw32/share/gdb/syscalls/mips-n32-linux.xml
index 5e77d729e46..911cb4158ef 100644
--- a/mingw32/share/gdb/syscalls/mips-n32-linux.xml
+++ b/mingw32/share/gdb/syscalls/mips-n32-linux.xml
@@ -1,6 +1,6 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mingw64/share/gdb/syscalls/mips-n32-linux.xml b/mingw64/share/gdb/syscalls/mips-n32-linux.xml
index 5e77d729e46..911cb4158ef 100644
--- a/mingw64/share/gdb/syscalls/mips-n32-linux.xml
+++ b/mingw64/share/gdb/syscalls/mips-n32-linux.xml
@@ -1,6 +1,6 @@
-
-