@@ -457,6 +457,26 @@ info threads [-gid] [-stopped] [-running] [ID]...
unavailability like gdb.Value.is_optimized_out checks for
optimized out values.
+ ** New constants gdb.NOT_LVAL, gdb.LVAL_REGISTER and gdb.LVAL_MEMORY
+ to represent the lval_type of a value.
+
+ ** New read-only attribute gdb.Value.lval_type which indicates the
+ lval_type of the value. Currently supports not_lval, lval_register
+ and lval_memory. Any other type is mapped to not_lval. Additional
+ types may be supported in future.
+
+ ** Added gdb.unwinder.UnwindRegisterValueTransformer. This is the
+ base class for transformers which provide methods for transforming
+ unwind register values. Currently one such method, demangle, is
+ supported, which is called after obtaining a register value from an
+ unwinder and allows that value to be demangled before being used by
+ GDB.
+
+ ** New function gdb.unwinder.register_unwind_register_value_transformer
+ that can register an instance of a sub-class of
+ gdb.unwinder.UnwindRegisterValueTransformer as a transformer for
+ unwind register values.
+
* Guile API
** New type <gdb:color> for dealing with colors.
@@ -977,6 +977,22 @@ fetched when the value is needed, or when the @code{fetch_lazy}
method is invoked.
@end defvar
+@defvar Value.lval_type
+The lval_type of this @code{gdb.Value}. The value of this attribute can
+be one of:
+@table @code
+@item gdb.LVAL_REGISTER
+The @code{gdb.Value} is in a register.
+
+@item gdb.LVAL_MEMORY
+The @code{gdb.Value} is in memory.
+
+@item gdb.NOT_LVAL
+The @code{gdb.Value} is not an lval or not covered by any of the other codes.
+@end table
+Additional lval_types may be added in future.
+@end defvar
+
@defvar Value.bytes
The value of this attribute is a @code{bytes} object containing the
bytes that make up this @code{Value}'s complete value in little endian
@@ -3111,6 +3127,22 @@ no such value was passed.
@end defvar
@end deftp
+@deftp {class} gdb.unwinder.UnwindRegisterValueTransformer
+
+The @code{UnwindRegisterValueTransformer} class is a base class from
+which unwind register value transformers created by users can derive,
+though it is not required that they derive from this class, so long as
+they have the required @code{name} attribute.
+
+@defun UnwindRegisterValueTransformer.__init__ (name)
+The @var{name} is the name of this transformer.
+@end defun
+
+@defvar UnwindRegisterValueTransformer.name
+A read-only attribute which is a string, the name of this transformer.
+@end defvar
+@end deftp
+
@subheading Registering an Unwinder
Object files and program spaces can have unwinders registered with
@@ -3199,6 +3231,69 @@ the matching unwinders are enabled. The @code{enabled} field of each
matching unwinder is set to @code{True}.
@end table
+@subheading Transforming Unwind Register Values
+
+An unwind register value transformer provides methods for transforming
+unwind register values. Currently only one type of transform,
+demangling, is supported, by implementing the @code{demangle} method.
+Additional transforms may be added in future.
+
+After getting a register value from an unwinder, @value{GDBN} will call
+the @code{demangle} method of registered transformers to allow them to
+demangle the value. This is useful in case a register value needs
+demangling before @value{GDBN} uses it and avoids the need to write a
+new unwinder.
+
+@subheading Registering an Unwind Register Value Transformer
+
+Currently, only one transformer can be registered at any one time and it
+is registered globally. Attempting to register more than one
+transformer or to a different locus will raise an exception.
+Registering more than one transformer or to different loci may be
+supported in future.
+
+The @code{gdb.unwinder} module provides the function to register a
+transformer:
+
+@defun gdb.unwinder.register_unwind_register_value_transformer (locus, transformer, replace=False)
+@var{locus} specifies to which transformer list to prepend the
+@var{transformer}. Only @code{None} is supported, which registers the
+transformer globally. Attempting to register to any other locus will
+raise an exception. Only one transformer can be registered at any one
+time. An attempt to add a second transformer raises an exception unless
+@var{replace} is @code{True} and the new and old transformer have the
+same name, in which case the old transformer is deleted and the new
+transformer is registered in its place.
+@end defun
+
+@subheading Skeleton Code for an Unwind Register Value Transformer
+
+Here is an example of how to structure a user created transformer:
+
+@smallexample
+from gdb.unwinder import UnwindRegisterValueTransformer
+
+class MyUnwindRegisterValueTransformer(UnwindRegisterValueTransformer):
+ def __init__(self):
+ super().__init__("MyTransformer_Name")
+
+ def demangle(self, frame, reg, value):
+ """
+ Return new value if demangled, otherwise None.
+ """
+ if not <frame we need to handle>:
+ return None
+
+ if <reg needs demangling>:
+ new_value = ... compute demangled value ...
+ return new_value
+
+ return None
+
+my_transformer = MyUnwindRegisterValueTransformer()
+gdb.unwinder.register_unwind_register_value_transformer(None, my_transformer, False)
+@end smallexample
+
@node Xmethods In Python
@subsubsection Xmethods In Python
@cindex xmethods in Python
@@ -310,6 +310,14 @@ struct extension_language_ops
program_space *pspace,
const struct bfd_build_id *build_id,
const char *filename);
+
+ /* Update the unwind VALUE of a register. Return EXT_LANG_RC_OK if VALUE was
+ updated, EXT_LANG_RC_NOP if it was left unchanged and EXT_LANG_RC_ERROR if
+ there was an error. */
+
+ enum ext_lang_rc (*demangle_unwind_register_value) (frame_info_ptr frame,
+ int regnum,
+ struct value **value);
};
/* State necessary to restore a signal handler to its previous value. */
@@ -1139,6 +1139,41 @@ ext_lang_before_prompt (const char *current_gdb_prompt)
}
}
+/* See extension.h. */
+
+struct value *
+ext_lang_demangle_unwind_register_value (frame_info_ptr frame, int regnum,
+ struct value *val)
+{
+ gdb_assert (val != nullptr);
+ for (const struct extension_language_defn *extlang : extension_languages)
+ {
+ enum ext_lang_rc rc;
+
+ if (extlang->ops == nullptr
+ || extlang->ops->demangle_unwind_register_value == nullptr)
+ continue;
+
+ rc = extlang->ops->demangle_unwind_register_value (frame, regnum, &val);
+ /* If the demangler changed the value or errored then do not call any
+ others. */
+ switch (rc)
+ {
+ case EXT_LANG_RC_OK:
+ case EXT_LANG_RC_ERROR:
+ return val;
+ /* If the demangler did not change the value then continue calling other
+ demanglers for other extension languages. */
+ case EXT_LANG_RC_NOP:
+ break;
+ default:
+ gdb_assert_not_reached (
+ "bad return from demangle_unwind_register_value");
+ }
+ }
+ return val;
+}
+
INIT_GDB_FILE (extension)
{
gdb::observers::before_prompt.attach (ext_lang_before_prompt, "extension");
@@ -433,6 +433,12 @@ extern ext_lang_missing_file_result ext_lang_find_objfile_from_buildid
(program_space *pspace, const struct bfd_build_id *build_id,
const char *filename);
+/* Take the unwound VALUE of the register identified by REGNUM being unwound
+ from FRAME and return the demangled value. */
+
+extern struct value * ext_lang_demangle_unwind_register_value
+ (frame_info_ptr frame, int regnum, struct value *value);
+
#if GDB_SELF_TEST
namespace selftests {
extern void (*hook_set_active_ext_lang) ();
@@ -1340,6 +1340,15 @@ frame_unwind_register_value (const frame_info_ptr &next_frame, int regnum)
user_reg_map_regnum_to_name (gdbarch, regnum));
}
+ /* Allow extension languages to demangle the unwound value.
+
+ Do not demangle before frame id has been computed as this would cause
+ recursion as gdbpy_demangle_unwind_register_value calls
+ frame_info_to_frame_object which calls get_frame_id.
+ */
+ if (next_frame->this_id.p == frame_id_status::COMPUTED)
+ value = ext_lang_demangle_unwind_register_value (next_frame, regnum, value);
+
if (frame_debug)
{
string_file debug_file;
@@ -138,6 +138,8 @@ static const struct extension_language_ops guile_extension_ops =
NULL, /* gdbscm_get_matching_xmethod_workers */
NULL, /* gdbscm_colorize */
NULL, /* gdbscm_print_insn */
+
+ nullptr, /* gdbscm_demangle_unwind_register_value */
};
#endif
@@ -104,6 +104,8 @@ frame_unwinders = []
# The missing file handlers. Each item is a tuple with the form
# (TYPE, HANDLER) where TYPE is a string either 'debug' or 'objfile'.
missing_file_handlers = []
+# Initial unwind register value transformers.
+unwind_register_value_transformers = []
def _execute_unwinders(pending_frame):
@@ -144,6 +146,25 @@ def _execute_unwinders(pending_frame):
return None
+def _execute_unwind_register_value_demanglers(frame, reg, value):
+ """Internal function called from GDB to execute unwind register value
+ demanglers.
+
+ Arguments:
+ frame: gdb.Frame instance.
+ reg: gdb.RegisterDescriptor instance.
+ value: gdb.Value instance.
+
+ Returns:
+ gdb.Value instance if value changed, otherwise None.
+ """
+ transformer_count = len(unwind_register_value_transformers)
+ assert transformer_count < 2
+
+ if transformer_count == 1:
+ return unwind_register_value_transformers[0].demangle(frame, reg, value)
+
+ return None
# Convenience variable to GDB's python directory
PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
@@ -16,7 +16,7 @@
"""Unwinder class and register_unwinder function."""
import gdb
-
+import _gdb.unwinder
class Unwinder(object):
"""Base class (or a template) for frame unwinders written in Python.
@@ -138,3 +138,90 @@ def register_unwinder(locus, unwinder, replace=False):
i += 1
locus.frame_unwinders.insert(0, unwinder)
gdb.invalidate_cached_frames()
+
+class UnwindRegisterValueTransformer(object):
+ """Base class (or a template) for unwind register value transformers written
+ in Python.
+ """
+ def __init__(self, name):
+ """Constructor.
+
+ Arguments:
+ name: An identifying name for the transformer.
+ """
+
+ if not isinstance(name, str):
+ raise TypeError("incorrect type for name: %s" % type(name))
+
+ self._name = name
+
+ @property
+ def name(self):
+ return self._name
+
+ def demangle(self, frame, reg, value):
+ """GDB calls this method to demangle a register unwind value.
+
+ Arguments:
+ frame: gdb.Frame instance.
+ reg: gdb.RegisterDescriptor instance.
+ value: gdb.Value instance.
+
+ Returns:
+ gdb.Value instance if value changed, otherwise None.
+ """
+ raise NotImplementedError("UnwindRegisterValueTransformer demangle.")
+
+def register_unwind_register_value_transformer(locus, transformer, replace=False):
+ """Register unwind register value transformer in given locus.
+
+ Currently only one transformer can be registered at any one time and
+ it must be registered globally.
+
+ Arguments:
+ locus: Must be None (which registers the transformer globally).
+ Any other locus will result in an exception.
+ transformer: An object of a gdb.unwinder.UnwindRegisterValueTransformer
+ subclass.
+ replace: If True, replaces existing transformer if it has the
+ same name. Otherwise, raises exception if there is
+ already a registered transformer.
+
+ Returns:
+ Nothing.
+
+ Raises:
+ TypeError: Bad locus type
+ RuntimeError: Only one transformer can be registered
+ """
+ if locus is None:
+ if gdb.parameter("verbose"):
+ gdb.write(
+ "Registering global unwind register value transformer '%s'\n"
+ % transformer.name)
+ locus = gdb
+ else:
+ raise TypeError("locus should be None")
+
+ transformer_count = len(locus.unwind_register_value_transformers)
+
+ assert transformer_count < 2
+
+ if transformer_count == 1:
+ if replace and transformer.name == locus.unwind_register_value_transformers[0].name:
+ del locus.unwind_register_value_transformers[0]
+ else:
+ raise RuntimeError("Only one unwind register value transformer can "
+ "be registered")
+
+ locus.unwind_register_value_transformers.insert(0, transformer)
+
+ # Call the private _set_transformer_enabled function within the
+ # _gdb.unwind module. This function sets a global flag within GDB's
+ # C++ code that enables or disables the Python unwind value
+ # transformer functionality, this improves performance of unwinding
+ # by avoiding unneeded calls into Python when we know that no
+ # transformers are registered.
+ _gdb.unwinder._set_transformer_enabled(True)
+
+ gdb.invalidate_cached_frames()
@@ -137,7 +137,7 @@ gdbpy_reggroup_name (PyObject *self, void *closure)
each REGNUM (in GDBARCH) only one descriptor is ever created, which is
then cached on the GDBARCH. */
-static gdbpy_ref<>
+gdbpy_ref<>
gdbpy_get_register_descriptor (struct gdbarch *gdbarch,
int regnum)
{
@@ -30,6 +30,7 @@
#include "stack.h"
#include "charset.h"
#include "block.h"
+#include "value.h"
/* Debugging of Python unwinders. */
@@ -1004,6 +1005,62 @@ pyuw_on_new_gdbarch (gdbarch *newarch)
}
}
+static bool python_transformer_enabled = false;
+
+/* Implement gdb._set_transformer_enabled function. Takes a boolean parameter
+ and sets whether GDB should enter the Python unwind register value
+ transformer code or not.
+
+ This is called from within the Python code when a new transformer is
+ registered. When no transformers are registered the global C++ flag is set
+ to false, and GDB never even enters the Python environment to check for a
+ transformer.
+
+ When the user registers a new Python transformer, the global C++ flag
+ is set to true, and now GDB will enter the Python environment to use the
+ transformer. */
+
+static PyObject *
+pyuw_set_transformer_enabled (PyObject *self, PyObject *args, PyObject *kw)
+{
+ PyObject *newstate;
+ static const char *keywords[] = { "state", nullptr };
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!", keywords,
+ &PyBool_Type, &newstate))
+ return nullptr;
+
+ python_transformer_enabled = newstate == Py_True;
+ Py_RETURN_NONE;
+}
+
+/* These are the methods we add into the _gdb.unwinder module, which
+ are then imported into the gdb.unwinder module. These are global
+ functions that support unwinding. */
+
+PyMethodDef python_unwinder_methods[] =
+{
+ { "_set_transformer_enabled", (PyCFunction) pyuw_set_transformer_enabled,
+ METH_VARARGS | METH_KEYWORDS,
+ "_set_transformer_enabled (STATE) -> None\n\
+Set whether GDB should call into the Python transformer code or not." },
+ {nullptr, nullptr, 0, nullptr}
+};
+
+/* Structure to define the _gdb.unwinder module. */
+
+static struct PyModuleDef python_unwinder_module_def =
+{
+ PyModuleDef_HEAD_INIT,
+ "_gdb.unwinder",
+ nullptr,
+ -1,
+ python_unwinder_methods,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr
+};
+
/* Initialize unwind machinery. */
static int
@@ -1017,6 +1074,19 @@ gdbpy_initialize_unwind ()
if (gdbpy_type_ready (&unwind_info_object_type) < 0)
return -1;
+ PyObject *gdb_unwinder_module;
+ gdb_unwinder_module = PyModule_Create (&python_unwinder_module_def);
+
+ if (gdb_pymodule_addobject (gdb_module, "unwinder",
+ gdb_unwinder_module) < 0)
+ return -1;
+
+ /* This is needed so that 'import _gdb.unwinder' will work. */
+ PyObject *dict = PyImport_GetModuleDict ();
+ if (PyDict_SetItemString (dict, "_gdb.unwinder",
+ gdb_unwinder_module) < 0)
+ return -1;
+
return 0;
}
@@ -1170,3 +1240,102 @@ PyTypeObject unwind_info_object_type =
0, /* tp_init */
0, /* tp_alloc */
};
+
+/* Execute python demanglers and handle the result. */
+
+enum ext_lang_rc
+gdbpy_demangle_unwind_register_value (frame_info_ptr frame, int regnum,
+ struct value **val)
+{
+ if (!gdb_python_initialized || !python_transformer_enabled)
+ return EXT_LANG_RC_ERROR;
+
+ gdbarch *gdbarch = get_frame_arch (frame);
+ gdbpy_enter enter_py (gdbarch);
+
+ /* Disable transformers whilst creating a python frame object if the frame is
+ the sentinel frame and regnum is pc. This is to avoid recursion as the
+ check for a corrupt stack in `frame_info_to_frame_object` results in
+ another call to this function with the same arguments due to the following
+ chain of calls:
+
+ get_prev_frame
+ -> get_frame_pc_if_available
+ -> frame_unwind_pc (frame->next)
+ -> ...
+ -> frame_unwind_register_value (frame, pc)
+ -> ...
+ -> gdbpy_demangle_unwind_register_value (frame, pc, ...)
+
+ The recursive call is passed the sentinel frame again because for the
+ sentinel frame: frame->next == frame. */
+ int pc_regnum = gdbarch_pc_regnum (gdbarch);
+ if (regnum == pc_regnum && is_sentinel_frame_id (get_frame_id (frame)))
+ python_transformer_enabled = false;
+ gdbpy_ref<> py_frame = frame_info_to_frame_object (frame);
+ if (regnum == pc_regnum && is_sentinel_frame_id (get_frame_id (frame)))
+ python_transformer_enabled = true;
+
+ gdbpy_ref<> py_reg_obj (gdbpy_get_register_descriptor (gdbarch, regnum));
+ if (py_reg_obj == nullptr)
+ {
+ gdbpy_print_stack ();
+ return EXT_LANG_RC_ERROR;
+ }
+
+ gdbpy_ref<> py_value_obj (value_to_value_object (*val));
+ if (py_value_obj == nullptr)
+ {
+ gdbpy_print_stack ();
+ return EXT_LANG_RC_ERROR;
+ }
+
+ /* Call demangler. */
+ const char *func_name = "_execute_unwind_register_value_demanglers";
+ if (gdb_python_module == nullptr
+ || ! PyObject_HasAttrString (gdb_python_module, func_name))
+ {
+ PyErr_SetString (PyExc_NameError,
+ "Installation error: gdb._execute_unwind_register_value_demanglers "
+ "function is missing");
+ gdbpy_print_stack ();
+ return EXT_LANG_RC_ERROR;
+ }
+ gdbpy_ref<> pyo_execute (
+ PyObject_GetAttrString (gdb_python_module, func_name));
+ if (pyo_execute == nullptr)
+ {
+ gdbpy_print_stack ();
+ return EXT_LANG_RC_ERROR;
+ }
+
+ /* Demangler should return a gdb.Value if value has been changed, otherwise
+ None. */
+ gdbpy_ref<> pyo_execute_ret
+ (PyObject_CallFunctionObjArgs (pyo_execute.get (), py_frame.get (),
+ py_reg_obj.get (), py_value_obj.get (),
+ nullptr));
+ if (pyo_execute_ret == nullptr)
+ {
+ gdbpy_print_stack ();
+ return EXT_LANG_RC_ERROR;
+ }
+ /* Use original value. */
+ if (pyo_execute_ret == Py_None)
+ return EXT_LANG_RC_NOP;
+
+ /* Verify the return value is a gdb.Value. */
+ struct value *new_val = convert_value_from_python (pyo_execute_ret.get ());
+
+ if (new_val == nullptr)
+ {
+ gdbpy_print_stack ();
+ error (_("The `demangle` method of an unwind register value transformer "
+ "should return a gdb.Value instance if the value is changed, "
+ "otherwise None"));
+ }
+ /* Finally update val to the demangled value. */
+ *val = new_val;
+
+ return EXT_LANG_RC_OK;
+}
@@ -1406,6 +1406,19 @@ valpy_fetch_lazy (PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
+/* Return the lval_type of this value. */
+static PyObject *
+valpy_get_lval_type (PyObject *self, void *closure)
+{
+ struct value *value = ((value_object *) self)->value;
+
+ enum lval_type type = value->lval ();
+ if (type != lval_register && type != lval_memory)
+ type = not_lval;
+
+ return gdb_py_object_from_longest (type).release ();
+}
+
/* Calculate and return the address of the PyObject as the value of
the builtin __hash__ call. */
static Py_hash_t
@@ -2245,6 +2258,12 @@ gdbpy_is_value_object (PyObject *obj)
static int
gdbpy_initialize_values ()
{
+ if (PyModule_AddIntConstant (gdb_module, "NOT_LVAL", not_lval) < 0
+ || PyModule_AddIntConstant (gdb_module, "LVAL_REGISTER",
+ lval_register) < 0
+ || PyModule_AddIntConstant (gdb_module, "LVAL_MEMORY", lval_memory) < 0)
+ return -1;
+
return gdbpy_type_ready (&value_object_type);
}
@@ -2269,6 +2288,8 @@ static gdb_PyGetSetDef value_object_getset[] = {
"Boolean telling whether the value is lazy (not fetched yet\n\
from the inferior). A lazy value is fetched when needed, or when\n\
the \"fetch_lazy()\" method is called.", NULL },
+ { "lval_type", valpy_get_lval_type, nullptr,
+ "Return the type of the lval", nullptr },
{ "bytes", valpy_get_bytes, valpy_set_bytes,
"Return a bytearray containing the bytes of this value.", nullptr },
{NULL} /* Sentinel */
@@ -447,6 +447,8 @@ extern enum ext_lang_bp_stop gdbpy_breakpoint_cond_says_stop
(const struct extension_language_defn *, struct breakpoint *);
extern int gdbpy_breakpoint_has_cond (const struct extension_language_defn *,
struct breakpoint *b);
+extern enum ext_lang_rc gdbpy_demangle_unwind_register_value
+ (frame_info_ptr frame, int regnum, struct value **val);
extern enum ext_lang_rc gdbpy_get_matching_xmethod_workers
(const struct extension_language_defn *extlang,
@@ -521,6 +523,7 @@ PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
gdbpy_ref<> gdbarch_to_arch_object (struct gdbarch *gdbarch);
PyObject *gdbpy_all_architecture_names (PyObject *self, PyObject *args);
+gdbpy_ref<> gdbpy_get_register_descriptor (struct gdbarch *gdbarch, int regnum);
PyObject *gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
const char *group_name);
PyObject *gdbpy_new_reggroup_iterator (struct gdbarch *gdbarch);
@@ -185,7 +185,9 @@ static const struct extension_language_ops python_extension_ops =
gdbpy_print_insn,
gdbpy_handle_missing_debuginfo,
- gdbpy_find_objfile_from_buildid
+ gdbpy_find_objfile_from_buildid,
+
+ gdbpy_demangle_unwind_register_value
};
#endif /* HAVE_PYTHON */
new file mode 100644
@@ -0,0 +1,33 @@
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from gdb.unwinder import UnwindRegisterValueTransformer
+
+class TestUnwindRegisterValueTransformer(UnwindRegisterValueTransformer):
+
+ def __init__(self):
+ super().__init__("test transformer")
+
+ def demangle(self, frame, reg, value):
+ """
+ Return a type that cannot be converted to a gdb.Value.
+ """
+ return []
+
+test_transformer = TestUnwindRegisterValueTransformer()
+gdb.unwinder.register_unwind_register_value_transformer(None, test_transformer,
+ True)
+
+print("Python script imported")
new file mode 100644
@@ -0,0 +1,80 @@
+/* This test program is part of GDB, the GNU debugger.
+
+ Copyright 2025 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This is the test program loaded into GDB by the py-unwind-transformer test.
+
+ The start/end of each non-inlined function mangles/demangles the saved RBP
+ and RIP to simulate a target that stores RBP and RIP in a mangled form on the
+ stack. */
+
+#define MY_FRAME ((volatile __UINTPTR_TYPE__ *)__builtin_frame_address (0))
+
+/* Mangle previous RBP and RIP using bitwise negation.
+
+ This is ABI-specifc. On AMD64, the word at RBP contains contains previous
+ RBP and the word at RBP+8 contains previous RIP (unless -fomit-frame-pointer
+ was enabled *and* the compiler was able to omit RBP). */
+
+#define MANGLE(FRAME) \
+ do \
+ { \
+ *FRAME = ~*FRAME; \
+ *(FRAME + 1) = ~*(FRAME + 1); \
+ } while (0)
+
+#define DEMANGLE MANGLE
+
+/* This function does not have its own frame when inlined so does not do any
+ mangling. */
+__attribute__ ((always_inline))
+static inline void
+f3_inline (void)
+{
+ __attribute__ ((unused))
+ volatile int x = 5;
+}
+
+static void
+f2 (void)
+{
+ MANGLE (MY_FRAME);
+
+ f3_inline ();
+
+ DEMANGLE (MY_FRAME);
+}
+
+static void
+f1 (void)
+{
+ MANGLE (MY_FRAME);
+
+ f2 ();
+
+ DEMANGLE (MY_FRAME);
+}
+
+int
+main ()
+{
+ MANGLE (MY_FRAME);
+
+ f1 ();
+
+ DEMANGLE (MY_FRAME);
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,115 @@
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite. It tests python support for unwind
+# register value transformers.
+
+# The transformer for this test is written for a hypothetical x86_64 target that
+# mangles RBP and RIP when saving them to the stack. The C file for this test
+# simulates this behaviour by mangling the saved RBP and RIP at the start of
+# each function and demangling them at the end.
+
+require allow_python_tests
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} {debug}] } {
+ return -1
+}
+
+# This test runs on a specific platform.
+require is_x86_64_m64_target
+
+# The following tests require execution.
+if {![runto_main]} {
+ return 0
+}
+
+# Continue to the innermost frame of the execution.
+gdb_breakpoint "f3_inline"
+gdb_continue_to_breakpoint "break in f3_inline"
+
+# Without the transformer sourced the backtrace should be incorrect.
+#
+# * Inline frame #0 is ok because it shares the same frame as #1.
+# * Frame #1 is ok because the frame address and program counter are in
+# registers RBP and RIP whose contents is not mangled.
+# * Frame #2 is not ok because its frame address and program counter are mangled
+# in memory.
+gdb_test_lines "bt 3" "broken backtrace when transformer not sourced" \
+[multi_line \
+ "#0 \[^\r\n\]* f3_inline \\(\\) at \[^\r\n\]+" \
+ "#1 \[^\r\n\]* f2 \\(\\) at \[^\r\n\]+" \
+ "#2 \[^\r\n\]* \\?\\? \\(\\)" \
+ "\\(More stack frames follow...\\)"]
+
+# Source the python unwind value transformer
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_test "source ${pyfile}" \
+ "Python script imported" \
+ "import python scripts"
+
+# The saved RBP and RIP should now be demangled before GDB uses them and the
+# backtrace should be correct.
+gdb_test_lines "bt" "correct backtrace when transformer sourced" \
+[multi_line \
+ "#0 \[^\r\n\]* f3_inline \\(\\) at \[^\r\n\]+" \
+ "#1 \[^\r\n\]* f2 \\(\\) at \[^\r\n\]+" \
+ "#2 \[^\r\n\]* f1 \\(\\) at \[^\r\n\]+" \
+ "#3 \[^\r\n\]* main \\(\\) at \[^\r\n\]+"]
+
+gdb_test_lines "frame 2" "select frame 2" \
+[multi_line \
+ "#2 \[^\r\n\]* f1 \\(\\) at \[^\r\n\]+" \
+ "\[0-9\]+.*f2 \\(\\);"]
+
+# Writing back to a demangled register is not currently supported. Check that
+# it fails gracefully.
+gdb_test "p \$pc = 0x12345678" "Attempt to assign to an unmodifiable value." \
+ "error writing back to demangled register"
+
+# Check for error message when trying to add too many transformers.
+# Same name, replace = False.
+gdb_test_no_output "python obj = UnwindRegisterValueTransformer('test_transformer')" \
+ "Instantiate transformer 1"
+gdb_test \
+ "python gdb.unwinder.register_unwind_register_value_transformer(None, obj)" \
+ "Only one unwind register value transformer can be registered" \
+ "exceed transformer limit: replace=False"
+# Different name, replace = True.
+gdb_test_no_output "python obj = UnwindRegisterValueTransformer('test_transformer2')" \
+ "Instantiate transformer 2"
+gdb_test \
+ "python gdb.unwinder.register_unwind_register_value_transformer(None, obj, True)" \
+ "Only one unwind register value transformer can be registered" \
+ "exceed transformer limit: replace=True"
+
+# Check for error message when trying to register a transformer to an
+# unsupported locus.
+gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
+gdb_test_no_output "python obj = UnwindRegisterValueTransformer('test_transformer')" \
+ "Instantiate transformer 3"
+gdb_test "python gdb.unwinder.register_unwind_register_value_transformer(pspace, obj)" "locus should be None" "unsupported locus"
+
+# Source a python unwind value transformer that returns an invalid type.
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}-error.py]
+# Check for error message on invalid return type.
+gdb_test "source ${pyfile}" \
+[multi_line \
+ "Python script imported" \
+ "Python Exception <class 'TypeError'>: Could not convert Python object: [].*" \
+ "The `demangle` method of an unwind register value transformer should return a gdb.Value instance if the value is changed, otherwise None.*"] \
+"error on invalid return type"
new file mode 100644
@@ -0,0 +1,48 @@
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from gdb.unwinder import UnwindRegisterValueTransformer
+
+class TestUnwindRegisterValueTransformer(UnwindRegisterValueTransformer):
+
+ def __init__(self):
+ super().__init__("test transformer")
+
+ def demangle1(self, value):
+ """
+ For this test case mangled values are the bitwise negation of the
+ original value.
+ """
+ mask = 0xffffffffffffffff
+ return value.cast(gdb.Value (mask).type) ^ mask
+
+ def demangle(self, frame, reg, value):
+ """
+ Demangle RBP or RIP if their value is from memory.
+ """
+ if frame.type() == gdb.SENTINEL_FRAME or frame.type() == gdb.INLINE_FRAME:
+ return None
+
+ if ((reg.name == 'rbp' or reg.name == 'rip')
+ and value.lval_type == gdb.LVAL_MEMORY):
+ return self.demangle1(value)
+
+ return None
+
+
+test_transformer = TestUnwindRegisterValueTransformer()
+gdb.unwinder.register_unwind_register_value_transformer(None, test_transformer, False)
+
+print("Python script imported")
@@ -257,6 +257,14 @@ proc test_value_in_inferior {} {
# Smoke-test is_optimized_out attribute
gdb_test "python print ('result = %s' % arg0.is_optimized_out)" "= False" "test is_optimized_out attribute"
+ # Test lval_type attribute
+ gdb_test "python print (gdb.parse_and_eval ('\$pc').lval_type == gdb.LVAL_REGISTER)" "True" "test lval_type attribute: lval_register"
+ gdb_test "python print (gdb.parse_and_eval ('*(int *)\$sp').lval_type == gdb.LVAL_MEMORY)" "True" "test lval_type attribute: lval_memory"
+ gdb_test "python print (gdb.Value(5).type.optimized_out().lval_type == gdb.NOT_LVAL)" "True" "test lval_type attribute: not_lval"
+ # Unsupported types are mapped to not_lval
+ gdb_test "p \$myvar = 3"
+ gdb_test "python print (gdb.parse_and_eval ('\$myvar').lval_type == gdb.NOT_LVAL)" "True" "test lval_type attribute: lval_internalvar"
+
# Test address attribute
gdb_test "python print ('result = %s' % arg0.address)" "= 0x\[\[:xdigit:\]\]+" "test address attribute"