command will now give an error. Previously the 'b' flag would
always override the 'r' flag.
+* New Commands
+
+info missing-debug-handler
+ List all the registered missing debug handlers.
+
+enable missing-debug-handler LOCUS HANDLER
+disable missing-debug-handler LOCUS HANDLER
+ Enable or disable a missing debug handler with a name matching the
+ regular expression HANDLER, in LOCUS.
+
+ LOCUS can be 'global' to operate on global missing debug handler,
+ 'progspace' to operate on handlers within the current program space,
+ or can be a regular expression which is matched against the filename
+ of the primary executable in each program space.
+
* Python API
** New function gdb.notify_mi(NAME, DATA), that emits custom
** New read/write attribute gdb.Value.bytes that contains a bytes
object holding the contents of this value.
+ ** New module gdb.missing_debug that facilitates dealing with
+ objfiles that are missing any debug information.
+
+ ** New function gdb.missing_debug.register_handler that can register
+ an instance of a sub-class of gdb.missing_debug.MissingDebugInfo
+ as a handler for objfiles that are missing debug information.
+
+ ** New class gdb.missing_debug.MissingDebugInfo which can be
+ sub-classed to create handlers for objfiles with missing debug
+ information.
+
* New commands
maintenance info linux-lwps
gdb/FrameDecorator.py \
gdb/FrameIterator.py \
gdb/frames.py \
+ gdb/missing_debug.py \
gdb/printing.py \
gdb/prompt.py \
gdb/styling.py \
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 \
* 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
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)
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
frame_filters = {}
# Initial frame unwinders.
frame_unwinders = []
+# Initial missing debug handlers.
+missing_debug_handlers = []
def _execute_unwinders(pending_frame):
# 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
--- /dev/null
+# 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()
--- /dev/null
+# 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)
/* The debug method list. */
PyObject *xmethods;
+
+ /* The missing debug handler list. */
+ PyObject *missing_debug_handlers;
};
extern PyTypeObject pspace_object_type
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);
}
if (self->xmethods == NULL)
return 0;
+ self->missing_debug_handlers = PyList_New (0);
+ if (self->missing_debug_handlers == nullptr)
+ return 0;
+
return 1;
}
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
"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 }
};
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. */
gdbpy_colorize_disasm,
gdbpy_print_insn,
+
+ gdbpy_handle_missing_debuginfo
};
#endif /* HAVE_PYTHON */
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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+# 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"
+}
--- /dev/null
+# 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")