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 @@ - -