- Patches dropped: * gdb-6.5-bz185337-resolve-tls-without-debuginfo-v2.patch - Patches updated: * gdb-6.6-buildid-locate.patch * gdb-add-missing-debug-info-python-hook.patch OBS-URL: https://build.opensuse.org/package/show/devel:gcc/gdb?expand=0&rev=414
1557 lines
53 KiB
Diff
1557 lines
53 KiB
Diff
From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
|
|
From: Andrew Burgess <aburgess@redhat.com>
|
|
Date: Sun, 15 Oct 2023 22:48:42 +0100
|
|
Subject: gdb-add-missing-debug-info-python-hook.patch
|
|
|
|
;; Backport upstream commit 8f6c452b5a4.
|
|
|
|
gdb: implement missing debug handler hook for Python
|
|
|
|
This commit builds on the previous commit, and implements the
|
|
extension_language_ops::handle_missing_debuginfo function for Python.
|
|
This hook will give user supplied Python code a chance to help find
|
|
missing debug information.
|
|
|
|
The implementation of the new hook is pretty minimal within GDB's C++
|
|
code; most of the work is out-sourced to a Python implementation which
|
|
is modelled heavily on how GDB's Python frame unwinders are
|
|
implemented.
|
|
|
|
The following new commands are added as commands implemented in
|
|
Python, this is similar to how the Python unwinder commands are
|
|
implemented:
|
|
|
|
info missing-debug-handlers
|
|
enable missing-debug-handler LOCUS HANDLER
|
|
disable missing-debug-handler LOCUS HANDLER
|
|
|
|
To make use of this extension hook a user will create missing debug
|
|
information handler objects, and registers these handlers with GDB.
|
|
When GDB encounters an objfile that is missing debug information, each
|
|
handler is called in turn until one is able to help. Here is a
|
|
minimal handler that does nothing useful:
|
|
|
|
import gdb
|
|
import gdb.missing_debug
|
|
|
|
class MyFirstHandler(gdb.missing_debug.MissingDebugHandler):
|
|
def __init__(self):
|
|
super().__init__("my_first_handler")
|
|
|
|
def __call__(self, objfile):
|
|
# This handler does nothing useful.
|
|
return None
|
|
|
|
gdb.missing_debug.register_handler(None, MyFirstHandler())
|
|
|
|
Returning None from the __call__ method tells GDB that this handler
|
|
was unable to find the missing debug information, and GDB should ask
|
|
any other registered handlers.
|
|
|
|
By extending the __call__ method it is possible for the Python
|
|
extension to locate the debug information for objfile and return a
|
|
value that tells GDB how to use the information that has been located.
|
|
|
|
Possible return values from a handler:
|
|
|
|
- None: This means the handler couldn't help. GDB will call other
|
|
registered handlers to see if they can help instead.
|
|
|
|
- False: The handler has done all it can, but the debug information
|
|
for the objfile still couldn't be found. GDB will not call
|
|
any other handlers, and will continue without the debug
|
|
information for objfile.
|
|
|
|
- True: The handler has installed the debug information into a
|
|
location where GDB would normally expect to find it. GDB
|
|
should look again for the debug information.
|
|
|
|
- A string: The handler can return a filename, which is the file
|
|
containing the missing debug information. GDB will load
|
|
this file.
|
|
|
|
When a handler returns True, GDB will look again for the debug
|
|
information, but only using the standard built-in build-id and
|
|
.gnu_debuglink based lookup strategies. It is not possible for an
|
|
extension to trigger another debuginfod lookup; the assumption is that
|
|
the debuginfod server is remote, and out of the control of extensions
|
|
running within GDB.
|
|
|
|
Handlers can be registered globally, or per program space. GDB checks
|
|
the handlers for the current program space first, and then all of the
|
|
global handles. The first handler that returns a value that is not
|
|
None, has "handled" the objfile, at which point GDB continues.
|
|
|
|
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
|
|
Approved-By: Tom Tromey <tom@tromey.com>
|
|
|
|
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
|
|
--- a/gdb/data-directory/Makefile.in
|
|
+++ b/gdb/data-directory/Makefile.in
|
|
@@ -73,6 +73,7 @@ PYTHON_FILE_LIST = \
|
|
gdb/FrameDecorator.py \
|
|
gdb/FrameIterator.py \
|
|
gdb/frames.py \
|
|
+ gdb/missing_debug.py \
|
|
gdb/printing.py \
|
|
gdb/prompt.py \
|
|
gdb/styling.py \
|
|
@@ -82,6 +83,7 @@ PYTHON_FILE_LIST = \
|
|
gdb/command/__init__.py \
|
|
gdb/command/explore.py \
|
|
gdb/command/frame_filters.py \
|
|
+ gdb/command/missing_debug.py \
|
|
gdb/command/pretty_printers.py \
|
|
gdb/command/prompt.py \
|
|
gdb/command/type_printers.py \
|
|
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
|
|
--- a/gdb/doc/python.texi
|
|
+++ b/gdb/doc/python.texi
|
|
@@ -229,6 +229,7 @@ optional arguments while skipping others. Example:
|
|
* Connections In Python:: Python representation of connections.
|
|
* TUI Windows In Python:: Implementing new TUI windows.
|
|
* Disassembly In Python:: Instruction Disassembly In Python
|
|
+* Missing Debug Info In Python:: Handle missing debug info from Python.
|
|
@end menu
|
|
|
|
@node Basic Python
|
|
@@ -5191,6 +5192,12 @@ The @code{frame_filters} attribute is a dictionary of frame filter
|
|
objects. @xref{Frame Filter API}, for more information.
|
|
@end defvar
|
|
|
|
+@defvar Progspace.missing_debug_handlers
|
|
+The @code{missing_debug_handlers} attribute is a list of the missing
|
|
+debug handler objects for this program space. @xref{Missing Debug
|
|
+Info In Python}, for more information.
|
|
+@end defvar
|
|
+
|
|
A program space has the following methods:
|
|
|
|
@defun Progspace.block_for_pc (pc)
|
|
@@ -7770,6 +7777,139 @@ class NibbleSwapDisassembler(gdb.disassembler.Disassembler):
|
|
gdb.disassembler.register_disassembler(NibbleSwapDisassembler())
|
|
@end smallexample
|
|
|
|
+@node Missing Debug Info In Python
|
|
+@subsubsection Missing Debug Info In Python
|
|
+@cindex python, handle missing debug information
|
|
+
|
|
+When @value{GDBN} encounters a new objfile (@pxref{Objfiles In
|
|
+Python}), e.g.@: the primary executable, or any shared libraries used
|
|
+by the inferior, @value{GDBN} will attempt to load the corresponding
|
|
+debug information for that objfile. The debug information might be
|
|
+found within the objfile itself, or within a separate objfile which
|
|
+@value{GDBN} will automatically locate and load.
|
|
+
|
|
+Sometimes though, @value{GDBN} might not find any debug information
|
|
+for an objfile, in this case the debugging experience will be
|
|
+restricted.
|
|
+
|
|
+If @value{GDBN} fails to locate any debug information for a particular
|
|
+objfile, there is an opportunity for a Python extension to step in. A
|
|
+Python extension can potentially locate the missing debug information
|
|
+using some platform- or project-specific steps, and inform
|
|
+@value{GDBN} of its location. Or a Python extension might provide
|
|
+some platform- or project-specific advice to the user about how to
|
|
+obtain the missing debug information.
|
|
+
|
|
+A missing debug information Python extension consists of a handler
|
|
+object which has the @code{name} and @code{enabled} attributes, and
|
|
+implements the @code{__call__} method. When @value{GDBN} encounters
|
|
+an objfile for which it is unable to find any debug information, it
|
|
+invokes the @code{__call__} method. Full details of how handlers are
|
|
+written can be found below.
|
|
+
|
|
+@subheading The @code{gdb.missing_debug} Module
|
|
+
|
|
+@value{GDBN} comes with a @code{gdb.missing_debug} module which
|
|
+contains the following class and global function:
|
|
+
|
|
+@deftp{class} gdb.missing_debug.MissingDebugHandler
|
|
+
|
|
+@code{MissingDebugHandler} is a base class from which user-created
|
|
+handlers can derive, though it is not required that handlers derive
|
|
+from this class, so long as any user created handler has the
|
|
+@code{name} and @code{enabled} attributes, and implements the
|
|
+@code{__call__} method.
|
|
+
|
|
+@defun MissingDebugHandler.__init__ (name)
|
|
+The @var{name} is a string used to reference this missing debug
|
|
+handler within some @value{GDBN} commands. Valid names consist of the
|
|
+characters @code{[-_a-zA-Z0-9]}, creating a handler with an invalid
|
|
+name raises a @code{ValueError} exception.
|
|
+@end defun
|
|
+
|
|
+@defun MissingDebugHandler.__call__ (objfile)
|
|
+Sub-classes must override the @code{__call__} method. The
|
|
+@var{objfile} argument will be a @code{gdb.Objfile}, this is the
|
|
+objfile for which @value{GDBN} was unable to find any debug
|
|
+information.
|
|
+
|
|
+The return value from the @code{__call__} method indicates what
|
|
+@value{GDBN} should do next. The possible return values are:
|
|
+
|
|
+@itemize @bullet
|
|
+@item @code{None}
|
|
+
|
|
+This indicates that this handler could not help with @var{objfile},
|
|
+@value{GDBN} should call any other registered handlers.
|
|
+
|
|
+@item @code{True}
|
|
+
|
|
+This indicates that this handler has installed the debug information
|
|
+into a location where @value{GDBN} would normally expect to find it
|
|
+when looking for separate debug information files (@pxref{Separate
|
|
+Debug Files}). @value{GDBN} will repeat the normal lookup process,
|
|
+which should now find the separate debug file.
|
|
+
|
|
+If @value{GDBN} still doesn't find the separate debug information file
|
|
+after this second attempt, then the Python missing debug information
|
|
+handlers are not invoked a second time, this prevents a badly behaved
|
|
+handler causing @value{GDBN} to get stuck in a loop. @value{GDBN}
|
|
+will continue without any debug information for @var{objfile}.
|
|
+
|
|
+@item @code{False}
|
|
+
|
|
+This indicates that this handler has done everything that it intends
|
|
+to do with @var{objfile}, but no separate debug information can be
|
|
+found. @value{GDBN} will not call any other registered handlers for
|
|
+@var{objfile}. @value{GDBN} will continue without debugging
|
|
+information for @var{objfile}.
|
|
+
|
|
+@item A string
|
|
+
|
|
+The returned string should contain a filename. @value{GDBN} will not
|
|
+call any further registered handlers, and will instead load the debug
|
|
+information from the file identified by the returned filename.
|
|
+@end itemize
|
|
+
|
|
+Invoking the @code{__call__} method from this base class will raise a
|
|
+@code{NotImplementedError} exception.
|
|
+@end defun
|
|
+
|
|
+@defvar MissingDebugHandler.name
|
|
+A read-only attribute which is a string, the name of this handler
|
|
+passed to the @code{__init__} method.
|
|
+@end defvar
|
|
+
|
|
+@defvar MissingDebugHandler.enabled
|
|
+A modifiable attribute containing a boolean; when @code{True}, the
|
|
+handler is enabled, and will be used by @value{GDBN}. When
|
|
+@code{False}, the handler has been disabled, and will not be used.
|
|
+@end defvar
|
|
+@end deftp
|
|
+
|
|
+@defun gdb.missing_debug.register_handler (locus, handler, replace=@code{False})
|
|
+Register a new missing debug handler with @value{GDBN}.
|
|
+
|
|
+@var{handler} is an instance of a sub-class of
|
|
+@code{MissingDebugHandler}, or at least an instance of an object that
|
|
+has the same attributes and methods as @code{MissingDebugHandler}.
|
|
+
|
|
+@var{locus} specifies to which handler list to prepend @var{handler}.
|
|
+It can be either a @code{gdb.Progspace} (@pxref{Progspaces In Python})
|
|
+or @code{None}, in which case the handler is registered globally. The
|
|
+newly registered @var{handler} will be called before any other handler
|
|
+from the same locus. Two handlers in the same locus cannot have the
|
|
+same name, an attempt to add a handler with an already existing name
|
|
+raises an exception unless @var{replace} is @code{True}, in which case
|
|
+the old handler is deleted and the new handler is prepended to the
|
|
+selected handler list.
|
|
+
|
|
+@value{GDBN} first calls the handlers for the current program space,
|
|
+and then the globally registered handlers. As soon as a handler
|
|
+returns a value other than @code{None}, no further handlers are called
|
|
+for this objfile.
|
|
+@end defun
|
|
+
|
|
@node Python Auto-loading
|
|
@subsection Python Auto-loading
|
|
@cindex Python auto-loading
|
|
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
|
|
--- a/gdb/python/lib/gdb/__init__.py
|
|
+++ b/gdb/python/lib/gdb/__init__.py
|
|
@@ -84,6 +84,8 @@ xmethods = []
|
|
frame_filters = {}
|
|
# Initial frame unwinders.
|
|
frame_unwinders = []
|
|
+# Initial missing debug handlers.
|
|
+missing_debug_handlers = []
|
|
|
|
|
|
def _execute_unwinders(pending_frame):
|
|
@@ -291,3 +293,42 @@ class Thread(threading.Thread):
|
|
# 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/gdb/python/lib/gdb/command/missing_debug.py b/gdb/python/lib/gdb/command/missing_debug.py
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/gdb/python/lib/gdb/command/missing_debug.py
|
|
@@ -0,0 +1,226 @@
|
|
+# Missing debug related commands.
|
|
+#
|
|
+# Copyright 2023 Free Software Foundation, Inc.
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; either version 3 of the License, or
|
|
+# (at your option) any later version.
|
|
+#
|
|
+# This program is distributed in the hope that it will be useful,
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+# GNU General Public License for more details.
|
|
+#
|
|
+# You should have received a copy of the GNU General Public License
|
|
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+
|
|
+import gdb
|
|
+import re
|
|
+
|
|
+
|
|
+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 <no-file>:"
|
|
+ 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/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/gdb/python/lib/gdb/missing_debug.py
|
|
@@ -0,0 +1,169 @@
|
|
+# Copyright (C) 2023 Free Software Foundation, Inc.
|
|
+
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; either version 3 of the License, or
|
|
+# (at your option) any later version.
|
|
+#
|
|
+# This program is distributed in the hope that it will be useful,
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+# GNU General Public License for more details.
|
|
+#
|
|
+# You should have received a copy of the GNU General Public License
|
|
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+
|
|
+"""
|
|
+MissingDebugHandler base class, and register_handler function.
|
|
+"""
|
|
+
|
|
+import gdb
|
|
+
|
|
+
|
|
+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 ch.isascii() or not (ch.isalnum() 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/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
|
|
--- a/gdb/python/py-progspace.c
|
|
+++ b/gdb/python/py-progspace.c
|
|
@@ -54,6 +54,9 @@ struct pspace_object
|
|
|
|
/* The debug method list. */
|
|
PyObject *xmethods;
|
|
+
|
|
+ /* The missing debug handler list. */
|
|
+ PyObject *missing_debug_handlers;
|
|
};
|
|
|
|
extern PyTypeObject pspace_object_type
|
|
@@ -163,6 +166,7 @@ pspy_dealloc (PyObject *self)
|
|
Py_XDECREF (ps_self->frame_unwinders);
|
|
Py_XDECREF (ps_self->type_printers);
|
|
Py_XDECREF (ps_self->xmethods);
|
|
+ Py_XDECREF (ps_self->missing_debug_handlers);
|
|
Py_TYPE (self)->tp_free (self);
|
|
}
|
|
|
|
@@ -198,6 +202,10 @@ pspy_initialize (pspace_object *self)
|
|
if (self->xmethods == NULL)
|
|
return 0;
|
|
|
|
+ self->missing_debug_handlers = PyList_New (0);
|
|
+ if (self->missing_debug_handlers == nullptr)
|
|
+ return 0;
|
|
+
|
|
return 1;
|
|
}
|
|
|
|
@@ -352,6 +360,47 @@ pspy_get_xmethods (PyObject *o, void *ignore)
|
|
return self->xmethods;
|
|
}
|
|
|
|
+/* Return the list of missing debug handlers for this program space. */
|
|
+
|
|
+static PyObject *
|
|
+pspy_get_missing_debug_handlers (PyObject *o, void *ignore)
|
|
+{
|
|
+ pspace_object *self = (pspace_object *) o;
|
|
+
|
|
+ Py_INCREF (self->missing_debug_handlers);
|
|
+ return self->missing_debug_handlers;
|
|
+}
|
|
+
|
|
+/* Set this program space's list of missing debug handlers to HANDLERS. */
|
|
+
|
|
+static int
|
|
+pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers,
|
|
+ void *ignore)
|
|
+{
|
|
+ pspace_object *self = (pspace_object *) o;
|
|
+
|
|
+ if (handlers == nullptr)
|
|
+ {
|
|
+ PyErr_SetString (PyExc_TypeError,
|
|
+ "cannot delete the missing debug handlers list");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (!PyList_Check (handlers))
|
|
+ {
|
|
+ PyErr_SetString (PyExc_TypeError,
|
|
+ "the missing debug handlers attribute must be a list");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* Take care in case the LHS and RHS are related somehow. */
|
|
+ gdbpy_ref<> tmp (self->missing_debug_handlers);
|
|
+ Py_INCREF (handlers);
|
|
+ self->missing_debug_handlers = handlers;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
/* Set the 'type_printers' attribute. */
|
|
|
|
static int
|
|
@@ -744,6 +793,8 @@ static gdb_PyGetSetDef pspace_getset[] =
|
|
"Type printers.", NULL },
|
|
{ "xmethods", pspy_get_xmethods, NULL,
|
|
"Debug methods.", NULL },
|
|
+ { "missing_debug_handlers", pspy_get_missing_debug_handlers,
|
|
+ pspy_set_missing_debug_handlers, "Missing debug handlers.", NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
diff --git a/gdb/python/python.c b/gdb/python/python.c
|
|
--- a/gdb/python/python.c
|
|
+++ b/gdb/python/python.c
|
|
@@ -124,7 +124,9 @@ static enum ext_lang_rc gdbpy_before_prompt_hook
|
|
static gdb::optional<std::string> gdbpy_colorize
|
|
(const std::string &filename, const std::string &contents);
|
|
static gdb::optional<std::string> gdbpy_colorize_disasm
|
|
- (const std::string &content, gdbarch *gdbarch);
|
|
+(const std::string &content, gdbarch *gdbarch);
|
|
+static ext_lang_missing_debuginfo_result gdbpy_handle_missing_debuginfo
|
|
+ (const struct extension_language_defn *extlang, struct objfile *objfile);
|
|
|
|
/* The interface between gdb proper and loading of python scripts. */
|
|
|
|
@@ -170,6 +172,8 @@ static const struct extension_language_ops python_extension_ops =
|
|
gdbpy_colorize_disasm,
|
|
|
|
gdbpy_print_insn,
|
|
+
|
|
+ gdbpy_handle_missing_debuginfo
|
|
};
|
|
|
|
#endif /* HAVE_PYTHON */
|
|
@@ -1661,6 +1665,83 @@ gdbpy_get_current_objfile (PyObject *unused1, PyObject *unused2)
|
|
return objfile_to_objfile_object (gdbpy_current_objfile).release ();
|
|
}
|
|
|
|
+/* Implement the 'handle_missing_debuginfo' hook for Python. GDB has
|
|
+ failed to find any debug information for OBJFILE. The extension has a
|
|
+ chance to record this, or even install the required debug information.
|
|
+ See the description of ext_lang_missing_debuginfo_result in
|
|
+ extension-priv.h for details of the return value. */
|
|
+
|
|
+static ext_lang_missing_debuginfo_result
|
|
+gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang,
|
|
+ struct objfile *objfile)
|
|
+{
|
|
+ /* Early exit if Python is not initialised. */
|
|
+ if (!gdb_python_initialized)
|
|
+ return {};
|
|
+
|
|
+ struct gdbarch *gdbarch = objfile->arch ();
|
|
+
|
|
+ gdbpy_enter enter_py (gdbarch);
|
|
+
|
|
+ /* Convert OBJFILE into the corresponding Python object. */
|
|
+ gdbpy_ref<> pyo_objfile = objfile_to_objfile_object (objfile);
|
|
+ if (pyo_objfile == nullptr)
|
|
+ {
|
|
+ gdbpy_print_stack ();
|
|
+ return {};
|
|
+ }
|
|
+
|
|
+ /* Lookup the helper function within the GDB module. */
|
|
+ gdbpy_ref<> pyo_handler
|
|
+ (PyObject_GetAttrString (gdb_python_module, "_handle_missing_debuginfo"));
|
|
+ if (pyo_handler == nullptr)
|
|
+ {
|
|
+ gdbpy_print_stack ();
|
|
+ return {};
|
|
+ }
|
|
+
|
|
+ /* Call the function, passing in the Python objfile object. */
|
|
+ gdbpy_ref<> pyo_execute_ret
|
|
+ (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_objfile.get (),
|
|
+ nullptr));
|
|
+ if (pyo_execute_ret == nullptr)
|
|
+ {
|
|
+ /* If the handler is cancelled due to a Ctrl-C, then propagate
|
|
+ the Ctrl-C as a GDB exception instead of swallowing it. */
|
|
+ gdbpy_print_stack_or_quit ();
|
|
+ return {};
|
|
+ }
|
|
+
|
|
+ /* Parse the result, and convert it back to the C++ object. */
|
|
+ if (pyo_execute_ret == Py_None)
|
|
+ return {};
|
|
+
|
|
+ if (PyBool_Check (pyo_execute_ret.get ()))
|
|
+ {
|
|
+ bool try_again = PyObject_IsTrue (pyo_execute_ret.get ());
|
|
+ return ext_lang_missing_debuginfo_result (try_again);
|
|
+ }
|
|
+
|
|
+ if (!gdbpy_is_string (pyo_execute_ret.get ()))
|
|
+ {
|
|
+ PyErr_SetString (PyExc_ValueError,
|
|
+ "return value from _handle_missing_debuginfo should "
|
|
+ "be None, a Bool, or a String");
|
|
+ gdbpy_print_stack ();
|
|
+ return {};
|
|
+ }
|
|
+
|
|
+ gdb::unique_xmalloc_ptr<char> filename
|
|
+ = python_string_to_host_string (pyo_execute_ret.get ());
|
|
+ if (filename == nullptr)
|
|
+ {
|
|
+ gdbpy_print_stack ();
|
|
+ return {};
|
|
+ }
|
|
+
|
|
+ return ext_lang_missing_debuginfo_result (std::string (filename.get ()));
|
|
+}
|
|
+
|
|
/* Compute the list of active python type printers and store them in
|
|
EXT_PRINTERS->py_type_printers. The product of this function is used by
|
|
gdbpy_apply_type_printers, and freed by gdbpy_free_type_printers.
|
|
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.c b/gdb/testsuite/gdb.python/py-missing-debug.c
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/gdb/testsuite/gdb.python/py-missing-debug.c
|
|
@@ -0,0 +1,22 @@
|
|
+/* This test program is part of GDB, the GNU debugger.
|
|
+
|
|
+ Copyright 2023 Free Software Foundation, Inc.
|
|
+
|
|
+ This program is free software; you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation; either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ This program is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+int
|
|
+main ()
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.exp b/gdb/testsuite/gdb.python/py-missing-debug.exp
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/gdb/testsuite/gdb.python/py-missing-debug.exp
|
|
@@ -0,0 +1,473 @@
|
|
+# Copyright (C) 2023 Free Software Foundation, Inc.
|
|
+
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; either version 3 of the License, or
|
|
+# (at your option) any later version.
|
|
+#
|
|
+# This program is distributed in the hope that it will be useful,
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+# GNU General Public License for more details.
|
|
+#
|
|
+# You should have received a copy of the GNU General Public License
|
|
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+
|
|
+load_lib gdb-python.exp
|
|
+
|
|
+require allow_python_tests
|
|
+
|
|
+standard_testfile
|
|
+
|
|
+if {[build_executable "failed to prepare" ${testfile} ${srcfile}]} {
|
|
+ return -1
|
|
+}
|
|
+
|
|
+# Remove debug information from BINFILE and place it into
|
|
+# BINFILE.debug.
|
|
+if {[gdb_gnu_strip_debug $binfile]} {
|
|
+ unsupported "cannot produce separate debug info files"
|
|
+ return -1
|
|
+}
|
|
+
|
|
+set remote_python_file \
|
|
+ [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
|
|
+
|
|
+set debug_filename ${binfile}.debug
|
|
+set hidden_filename ${binfile}.hidden
|
|
+
|
|
+# Start GDB.
|
|
+clean_restart
|
|
+
|
|
+# Some initial sanity checks; initially, we can find the debug information
|
|
+# (this will use the .gnu_debuglink), then after we move the debug
|
|
+# information, reload the executable, now the debug can't be found.
|
|
+with_test_prefix "initial checks" {
|
|
+ # Load BINFILE, we should find the separate debug information.
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "debug"} \
|
|
+ "debug info is found"
|
|
+
|
|
+ # Rename the debug information file, re-load BINFILE, GDB should fail
|
|
+ # to find the debug information
|
|
+ remote_exec build "mv $debug_filename $hidden_filename"
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
|
|
+ "debug info no longer found"
|
|
+}
|
|
+
|
|
+# Load the Python script into GDB.
|
|
+gdb_test "source $remote_python_file" "^Success" \
|
|
+ "source python script"
|
|
+
|
|
+# Setup the separate debug info directory. This isn't actually needed until
|
|
+# some of the later tests, but might as well get this done now.
|
|
+set debug_directory [standard_output_file "debug-dir"]
|
|
+remote_exec build "mkdir -p $debug_directory"
|
|
+gdb_test_no_output "set debug-file-directory $debug_directory" \
|
|
+ "set debug-file-directory"
|
|
+
|
|
+# Initially the missing debug handler we install is in a mode where it
|
|
+# returns None, indicating that it can't help locate the debug information.
|
|
+# Check this works as expected.
|
|
+with_test_prefix "handler returning None" {
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(None, handler_obj)" \
|
|
+ "register the initial handler"
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
|
|
+ "debug info not found"
|
|
+
|
|
+ # Check the handler was only called once.
|
|
+ gdb_test "python print(handler_obj.call_count)" "^1" \
|
|
+ "check handler was only called once"
|
|
+}
|
|
+
|
|
+# Now configure the handler to move the debug file back to the
|
|
+# .gnu_debuglink location and then return True, this will cause GDB to
|
|
+# recheck, at which point it should find the debug info.
|
|
+with_test_prefix "handler in gnu_debuglink mode" {
|
|
+ gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
|
|
+ \"$hidden_filename\", \
|
|
+ \"$debug_filename\")" \
|
|
+ "confirgure handler"
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
|
|
+
|
|
+ # Check the handler was only called once.
|
|
+ gdb_test "python print(handler_obj.call_count)" "^1" \
|
|
+ "check handler was only called once"
|
|
+}
|
|
+
|
|
+# Setup a directory structure based on the build-id of BINFILE, but don't
|
|
+# move the debug information into place just yet.
|
|
+#
|
|
+# Instead, configure the handler to move the debug info into the build-id
|
|
+# directory.
|
|
+#
|
|
+# Reload BINFILE, at which point the handler will move the debug info into
|
|
+# the build-id directory and return True, GDB will then recheck for the
|
|
+# debug information, and should find it.
|
|
+with_test_prefix "handler in build-id mode" {
|
|
+ # Move the debug file out of the way once more.
|
|
+ remote_exec build "mv $debug_filename $hidden_filename"
|
|
+
|
|
+ # Create the build-id based directory in which the debug information
|
|
+ # will be placed.
|
|
+ set build_id_filename \
|
|
+ $debug_directory/[build_id_debug_filename_get $binfile]
|
|
+ remote_exec build "mkdir -p [file dirname $build_id_filename]"
|
|
+
|
|
+ # Configure the handler to move the debug info into the build-id dir.
|
|
+ gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
|
|
+ \"$hidden_filename\", \
|
|
+ \"$build_id_filename\")" \
|
|
+ "confirgure handler"
|
|
+
|
|
+ # Reload the binary and check the debug information is found.
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
|
|
+
|
|
+ # Check the handler was only called once.
|
|
+ gdb_test "python print(handler_obj.call_count)" "^1" \
|
|
+ "check handler was only called once"
|
|
+}
|
|
+
|
|
+# Move the debug information back to a hidden location and configure the
|
|
+# handler to return the filename of the hidden debug info location. GDB
|
|
+# should immediately use this file as the debug information.
|
|
+with_test_prefix "handler returning a string" {
|
|
+ remote_exec build "mv $build_id_filename $hidden_filename"
|
|
+
|
|
+ # Configure the handler return a filename string.
|
|
+ gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \
|
|
+ \"$hidden_filename\")" \
|
|
+ "confirgure handler"
|
|
+
|
|
+ # Reload the binary and check the debug information is found.
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
|
|
+
|
|
+ # Check the handler was only called once.
|
|
+ gdb_test "python print(handler_obj.call_count)" "^1" \
|
|
+ "check handler was only called once"
|
|
+}
|
|
+
|
|
+# Register another global handler, this one raises an exception. Reload the
|
|
+# debug information, the bad handler should be invoked first, which raises
|
|
+# an excetption, at which point GDB should skip further Python handlers.
|
|
+with_test_prefix "handler raises an exception" {
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(None, rhandler)"
|
|
+
|
|
+ foreach_with_prefix exception_type {gdb.GdbError TypeError} {
|
|
+ gdb_test_no_output \
|
|
+ "python rhandler.exception_type = $exception_type"
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
|
|
+ "debug info not found"
|
|
+
|
|
+ set re [string_to_regexp \
|
|
+ "Python Exception <class '$exception_type'>: message"]
|
|
+ gdb_assert {[regexp $re $gdb_file_cmd_msg]} \
|
|
+ "check for exception in file command output"
|
|
+
|
|
+ # Our original handler is still registered, but should not have been
|
|
+ # called again (as the exception occurs first).
|
|
+ gdb_test "python print(handler_obj.call_count)" "^1" \
|
|
+ "check good handler hasn't been called again"
|
|
+ }
|
|
+}
|
|
+
|
|
+gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Global:" \
|
|
+ " exception_handler" \
|
|
+ " handler"] \
|
|
+ "check both handlers are visible"
|
|
+
|
|
+# Re-start GDB.
|
|
+clean_restart
|
|
+
|
|
+# Load the Python script into GDB.
|
|
+gdb_test "source $remote_python_file" "^Success" \
|
|
+ "source python script for bad handler name checks"
|
|
+
|
|
+# Attempt to register a missing-debug-handler with NAME. The expectation is
|
|
+# that this should fail as NAME contains some invalid characters.
|
|
+proc check_bad_name {name} {
|
|
+ set name_re [string_to_regexp $name]
|
|
+ set re \
|
|
+ [multi_line \
|
|
+ "ValueError: invalid character '.' in handler name: $name_re" \
|
|
+ "Error while executing Python code\\."]
|
|
+
|
|
+ gdb_test "python register(\"$name\")" $re \
|
|
+ "check that '$name' is not accepted"
|
|
+}
|
|
+
|
|
+# We don't attempt to be exhaustive here, just check a few random examples
|
|
+# of invalid names.
|
|
+check_bad_name "!! Bad Name"
|
|
+check_bad_name "Bad Name"
|
|
+check_bad_name "(Bad Name)"
|
|
+check_bad_name "Bad \[Name\]"
|
|
+check_bad_name "Bad,Name"
|
|
+check_bad_name "Bad;Name"
|
|
+
|
|
+# Check that there are no handlers registered.
|
|
+gdb_test_no_output "info missing-debug-handlers" \
|
|
+ "check no handlers are registered"
|
|
+
|
|
+# Check we can use the enable/disable commands where there are no handlers
|
|
+# registered.
|
|
+gdb_test "enable missing-debug-handler foo" \
|
|
+ "^0 missing debug handlers enabled"
|
|
+gdb_test "disable missing-debug-handler foo" \
|
|
+ "^0 missing debug handlers disabled"
|
|
+
|
|
+# Grab the current program space object, used for registering handler later.
|
|
+gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
|
|
+
|
|
+# Now register some handlers.
|
|
+foreach hspec {{\"Foo\" None}
|
|
+ {\"-bar\" None}
|
|
+ {\"baz-\" pspace}
|
|
+ {\"abc-def\" pspace}} {
|
|
+ lassign $hspec name locus
|
|
+ gdb_test "python register($name, $locus)"
|
|
+}
|
|
+
|
|
+with_test_prefix "all handlers enabled" {
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Current Progspace:" \
|
|
+ " abc-def" \
|
|
+ " baz-" \
|
|
+ "Global:" \
|
|
+ " -bar" \
|
|
+ " Foo"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+with_test_prefix "disable 'baz-'" {
|
|
+ gdb_test "disable missing-debug-handler progspace baz-" \
|
|
+ "^1 missing debug handler disabled"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " abc-def" \
|
|
+ " baz- \\\[disabled\\\]" \
|
|
+ "Global:" \
|
|
+ " -bar" \
|
|
+ " Foo"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['abc-def', '-bar', 'Foo']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+with_test_prefix "disable 'Foo'" {
|
|
+ gdb_test "disable missing-debug-handler .* Foo" \
|
|
+ "^1 missing debug handler disabled"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " abc-def" \
|
|
+ " baz- \\\[disabled\\\]" \
|
|
+ "Global:" \
|
|
+ " -bar" \
|
|
+ " Foo \\\[disabled\\\]"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['abc-def', '-bar']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+with_test_prefix "disable everything" {
|
|
+ gdb_test "disable missing-debug-handler .* .*" \
|
|
+ "^2 missing debug handlers disabled"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " abc-def \\\[disabled\\\]" \
|
|
+ " baz- \\\[disabled\\\]" \
|
|
+ "Global:" \
|
|
+ " -bar \\\[disabled\\\]" \
|
|
+ " Foo \\\[disabled\\\]"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {[]}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+with_test_prefix "enable 'abc-def'" {
|
|
+ set re [string_to_regexp $binfile]
|
|
+
|
|
+ gdb_test "enable missing-debug-handler \"$re\" abc-def" \
|
|
+ "^1 missing debug handler enabled"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " abc-def" \
|
|
+ " baz- \\\[disabled\\\]" \
|
|
+ "Global:" \
|
|
+ " -bar \\\[disabled\\\]" \
|
|
+ " Foo \\\[disabled\\\]"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['abc-def']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+with_test_prefix "enable global handlers" {
|
|
+ set re [string_to_regexp $binfile]
|
|
+
|
|
+ gdb_test "enable missing-debug-handler global" \
|
|
+ "^2 missing debug handlers enabled"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " abc-def" \
|
|
+ " baz- \\\[disabled\\\]" \
|
|
+ "Global:" \
|
|
+ " -bar" \
|
|
+ " Foo"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['abc-def', '-bar', 'Foo']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+# Add handler_obj to the global handler list, and configure it to
|
|
+# return False. We should call all of the program space specific
|
|
+# handlers (which return None), and then call handler_obj from the
|
|
+# global list, which returns False, at which point we shouldn't call
|
|
+# anyone else.
|
|
+with_test_prefix "return False handler in progspace list" {
|
|
+ gdb_test "enable missing-debug-handler progspace" \
|
|
+ "^1 missing debug handler enabled"
|
|
+
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(None, handler_obj)" \
|
|
+ "register the initial handler"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " abc-def" \
|
|
+ " baz-" \
|
|
+ "Global:" \
|
|
+ " handler" \
|
|
+ " -bar" \
|
|
+ " Foo"]
|
|
+
|
|
+ gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \
|
|
+ "confirgure handler"
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['abc-def', 'baz-', 'handler']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+# Now add handler_obj to the current program space's handler list. We
|
|
+# use the same handler object here, that's fine. We should only see a
|
|
+# call to the first handler object in the call log.
|
|
+with_test_prefix "return False handler in global list" {
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(pspace, handler_obj)" \
|
|
+ "register the initial handler"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " handler" \
|
|
+ " abc-def" \
|
|
+ " baz-" \
|
|
+ "Global:" \
|
|
+ " handler" \
|
|
+ " -bar" \
|
|
+ " Foo"]
|
|
+
|
|
+ gdb_file_cmd $binfile
|
|
+ gdb_test "python print(handler_call_log)" \
|
|
+ [string_to_regexp {['handler']}]
|
|
+ gdb_test_no_output "python handler_call_log = \[\]" \
|
|
+ "reset call log"
|
|
+}
|
|
+
|
|
+with_test_prefix "check handler replacement" {
|
|
+ # First, check we can have the same name appear in both program
|
|
+ # space and global lists without giving an error.
|
|
+ gdb_test_no_output "python register(\"Foo\", pspace)"
|
|
+
|
|
+ gdb_test "info missing-debug-handlers" \
|
|
+ [multi_line \
|
|
+ "Progspace \[^\r\n\]+:" \
|
|
+ " Foo" \
|
|
+ " handler" \
|
|
+ " abc-def" \
|
|
+ " baz-" \
|
|
+ "Global:" \
|
|
+ " handler" \
|
|
+ " -bar" \
|
|
+ " Foo"]
|
|
+
|
|
+ # Now check that we get an error if we try to add a handler with
|
|
+ # the same name.
|
|
+ gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
|
|
+ [multi_line \
|
|
+ "RuntimeError: Handler Foo already exists\\." \
|
|
+ "Error while executing Python code\\."]
|
|
+
|
|
+ gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \
|
|
+ [multi_line \
|
|
+ "RuntimeError: Handler Foo already exists\\." \
|
|
+ "Error while executing Python code\\."]
|
|
+
|
|
+ # And now try again, but this time with 'replace=True', we
|
|
+ # shouldn't get an error in this case.
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)"
|
|
+
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)"
|
|
+
|
|
+ # Now disable a handler and check we still need to use 'replace=True'.
|
|
+ gdb_test "disable missing-debug-handler progspace Foo" \
|
|
+ "^1 missing debug handler disabled"
|
|
+
|
|
+ gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
|
|
+ [multi_line \
|
|
+ "RuntimeError: Handler Foo already exists\\." \
|
|
+ "Error while executing Python code\\."] \
|
|
+ "still get an error when handler is disabled"
|
|
+
|
|
+ gdb_test_no_output \
|
|
+ "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \
|
|
+ "can replace a disabled handler"
|
|
+}
|
|
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.py b/gdb/testsuite/gdb.python/py-missing-debug.py
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/gdb/testsuite/gdb.python/py-missing-debug.py
|
|
@@ -0,0 +1,120 @@
|
|
+# Copyright (C) 2023 Free Software Foundation, Inc.
|
|
+
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; either version 3 of the License, or
|
|
+# (at your option) any later version.
|
|
+#
|
|
+# This program is distributed in the hope that it will be useful,
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+# GNU General Public License for more details.
|
|
+#
|
|
+# You should have received a copy of the GNU General Public License
|
|
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+
|
|
+import gdb
|
|
+from gdb.missing_debug import MissingDebugHandler
|
|
+from enum import Enum
|
|
+import os
|
|
+
|
|
+# A global log that is filled in by instances of the LOG_HANDLER class
|
|
+# when they are called.
|
|
+handler_call_log = []
|
|
+
|
|
+
|
|
+class Mode(Enum):
|
|
+ RETURN_NONE = 0
|
|
+ RETURN_TRUE = 1
|
|
+ RETURN_FALSE = 2
|
|
+ RETURN_STRING = 3
|
|
+
|
|
+
|
|
+class handler(MissingDebugHandler):
|
|
+ def __init__(self):
|
|
+ super().__init__("handler")
|
|
+ self._call_count = 0
|
|
+ self._mode = Mode.RETURN_NONE
|
|
+
|
|
+ def __call__(self, objfile):
|
|
+ global handler_call_log
|
|
+ handler_call_log.append(self.name)
|
|
+ self._call_count += 1
|
|
+ if self._mode == Mode.RETURN_NONE:
|
|
+ return None
|
|
+
|
|
+ if self._mode == Mode.RETURN_TRUE:
|
|
+ os.rename(self._src, self._dest)
|
|
+ return True
|
|
+
|
|
+ if self._mode == Mode.RETURN_FALSE:
|
|
+ return False
|
|
+
|
|
+ if self._mode == Mode.RETURN_STRING:
|
|
+ return self._dest
|
|
+
|
|
+ assert False
|
|
+
|
|
+ @property
|
|
+ def call_count(self):
|
|
+ """Return a count, the number of calls to __call__ since the last
|
|
+ call to set_mode.
|
|
+ """
|
|
+ return self._call_count
|
|
+
|
|
+ def set_mode(self, mode, *args):
|
|
+ self._call_count = 0
|
|
+ self._mode = mode
|
|
+
|
|
+ if mode == Mode.RETURN_NONE:
|
|
+ assert len(args) == 0
|
|
+ return
|
|
+
|
|
+ if mode == Mode.RETURN_TRUE:
|
|
+ assert len(args) == 2
|
|
+ self._src = args[0]
|
|
+ self._dest = args[1]
|
|
+ return
|
|
+
|
|
+ if mode == Mode.RETURN_FALSE:
|
|
+ assert len(args) == 0
|
|
+ return
|
|
+
|
|
+ if mode == Mode.RETURN_STRING:
|
|
+ assert len(args) == 1
|
|
+ self._dest = args[0]
|
|
+ return
|
|
+
|
|
+ assert False
|
|
+
|
|
+
|
|
+class exception_handler(MissingDebugHandler):
|
|
+ def __init__(self):
|
|
+ super().__init__("exception_handler")
|
|
+ self.exception_type = None
|
|
+
|
|
+ def __call__(self, objfile):
|
|
+ global handler_call_log
|
|
+ handler_call_log.append(self.name)
|
|
+ assert self.exception_type is not None
|
|
+ raise self.exception_type("message")
|
|
+
|
|
+
|
|
+class log_handler(MissingDebugHandler):
|
|
+ def __call__(self, objfile):
|
|
+ global handler_call_log
|
|
+ handler_call_log.append(self.name)
|
|
+ return None
|
|
+
|
|
+
|
|
+# A basic helper function, this keeps lines shorter in the TCL script.
|
|
+def register(name, locus=None):
|
|
+ gdb.missing_debug.register_handler(locus, log_handler(name))
|
|
+
|
|
+
|
|
+# Create instances of the handlers, but don't install any. We install
|
|
+# these as needed from the TCL script.
|
|
+rhandler = exception_handler()
|
|
+handler_obj = handler()
|
|
+
|
|
+print("Success")
|