Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [RFC PATCH] gdb: Add python support for demangling register unwind values
@ 2025-04-10 11:04 Craig Blackmore
  2025-04-10 12:31 ` Eli Zaretskii
  2025-05-08 10:17 ` [RFC PATCH v2] " Craig Blackmore
  0 siblings, 2 replies; 18+ messages in thread
From: Craig Blackmore @ 2025-04-10 11:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Craig Blackmore

This patch addresses the scenario where a register value is saved in
some mangled form on the target and gdb does not have sufficient
information to demangle it.

After getting a register value from an unwinder, call out to extension
languages to allow the value to be demangled.  This avoids having to
write a new unwinder and allows the standard unwinders to be used.

This approach is purely for demangling values that have been read, there
is no similar support provided for mangling values being written back to
the target as this was not something that I needed to be able to do.

Only one demangler can be registered at any one time.  In future, the
implementation could be extended to support multiple demanglers executed
in order of priority.

The demangler is registered globally.  In future, the implementation
could be extended to register per program space and object file too.

gdbpy_get_register_descriptor is now externally visible as it is used
to create a gdb.RegisterDescriptor object to pass to the demangler.

This patch adds gdb.Value.is_lval_{register,memory} variables so that
the demangler can choose to skip modifying values that did not come
directly from the target and may not need demangling, for example,
values taken directly from DWARF.
---
 gdb/NEWS                                      | 11 +++
 gdb/doc/python.texi                           | 48 +++++++++++
 gdb/extension-priv.h                          |  8 ++
 gdb/extension.c                               | 35 ++++++++
 gdb/extension.h                               |  6 ++
 gdb/frame.c                                   |  3 +
 gdb/guile/guile.c                             |  2 +
 gdb/python/lib/gdb/__init__.py                | 17 ++++
 gdb/python/lib/gdb/unwinder.py                | 34 ++++++++
 gdb/python/py-registers.c                     |  2 +-
 gdb/python/py-unwind.c                        | 80 +++++++++++++++++
 gdb/python/py-value.c                         | 25 ++++++
 gdb/python/python-internal.h                  |  4 +
 gdb/python/python.c                           |  4 +-
 .../gdb.python/py-unwind-demangler.c          | 80 +++++++++++++++++
 .../gdb.python/py-unwind-demangler.exp        | 86 +++++++++++++++++++
 .../gdb.python/py-unwind-demangler.py         | 44 ++++++++++
 gdb/testsuite/gdb.python/py-value.exp         |  8 ++
 18 files changed, 495 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.py

diff --git a/gdb/NEWS b/gdb/NEWS
index 99ec392d4c4..0de0841086d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -63,6 +63,17 @@ info sharedlibrary
 
 * Python API
 
+  ** Added gdb.UnwindRegisterValueDemangler.  This is the base class for Unwind
+     Register Value Demanglers which are called after obtaining a register value
+     from an unwinder and allow that value to be demangled before being used by
+     GDB.
+
+  ** New read-only boolean attribute gdb.Value.is_lval_register which indicates
+     if the value is from a register on the inferior.
+
+  ** New read-only boolean attribute gdb.Value.is_lval_memory which indicates
+     if the value is from inferior memory.
+
   ** New class gdb.Color for dealing with colors.
 
   ** New constant gdb.PARAM_COLOR represents color type of a
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 50342bbba5f..5d2ecdc833d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -938,6 +938,16 @@ fetched when the value is needed, or when the @code{fetch_lazy}
 method is invoked.  
 @end defvar
 
+@defvar Value.is_lval_register
+The value of this read-only boolean attribute is @code{True} if this
+@code{gdb.Value} is from a register on the inferior.
+@end defvar
+
+@defvar Value.is_lval_memory
+The value of this read-only boolean attribute is @code{True} if this
+@code{gdb.Value} is from inferior memory.
+@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
@@ -3160,6 +3170,44 @@ the matching unwinders are enabled.  The @code{enabled} field of each
 matching unwinder is set to @code{True}.
 @end table
 
+@subheading Unwind Register Value Demangler
+
+After getting a register value from an unwinder, @value{GDBN} will call
+out to extension language demanglers to allow them to modify 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.
+
+Currently, only one demangler can be registered at any one time and it
+is registered globally.
+
+@subheading Unwinder Register Value Demangler Skeleton Code
+
+Here is an example of how to structure a user created demangler:
+
+@smallexample
+from gdb.unwinder import UnwindRegisterValueDemangler
+
+class MyUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
+    def __init__(self):
+        # Register self as the one and only demangler
+        super().__init__("MyUnwinder_Name")
+
+    def __call__(self, frame_type, reg, value):
+        """
+        Return new value if demangled, otherwise None.
+        """
+        if frame_type != <frame we need to handle>:
+          return None
+
+        if reg.name == <reg that needs demangling>:
+          new_value = ... compute demangled value ...
+          return new_value
+
+        return None
+
+my_demangler = MyUnwindRegisterValueDemangler()
+@end smallexample
+
 @node Xmethods In Python
 @subsubsection Xmethods In Python
 @cindex xmethods in Python
diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index cde64a2ccd0..ded61b2b789 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -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.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index 6d14a201a4b..f4dbf0c2b33 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -1102,6 +1102,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 == NULL)
+	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;
+}
+
 void _initialize_extension ();
 void
 _initialize_extension ()
diff --git a/gdb/extension.h b/gdb/extension.h
index c607df89f4d..bfaff6f22ab 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -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) ();
diff --git a/gdb/frame.c b/gdb/frame.c
index fe5336f2401..1824a5ea83e 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1293,6 +1293,9 @@ 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.  */
+  value = ext_lang_demangle_unwind_register_value (next_frame, regnum, value);
+
   if (frame_debug)
     {
       string_file debug_file;
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index 1803df0d3c4..686171e2d63 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -137,6 +137,8 @@ static const struct extension_language_ops guile_extension_ops =
   NULL, /* gdbscm_get_matching_xmethod_workers */
   NULL, /* gdbscm_colorize */
   NULL, /* gdbscm_print_insn */
+
+  NULL, /* gdbscm_demangle_unwind_register_value */
 };
 #endif
 
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 4ea5d06a3ba..9f8c2655c52 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -90,6 +90,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 = []
+# Default unwind register demangler does nothing.
+unwind_register_value_demangler = None
 
 
 def _execute_unwinders(pending_frame):
@@ -130,6 +132,21 @@ def _execute_unwinders(pending_frame):
 
     return None
 
+def _execute_unwind_register_value_demangler(frame_type, reg, value):
+    """Internal function called from GDB to execute demangler.
+
+    Arguments:
+        frame_type: integer constant corresponding to one of the named
+                    constants returned by gdb.Frame.type ().
+        reg: gdb.RegisterDescriptor instance.
+        value: gdb.Value instance.
+
+    Returns:
+        gdb.Value instance if value changed, otherwise None.
+    """
+    if unwind_register_value_demangler is None:
+        return None
+    return unwind_register_value_demangler(frame_type, reg, value)
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
index 3e1f756f92f..78aa1f85b6a 100644
--- a/gdb/python/lib/gdb/unwinder.py
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -138,3 +138,37 @@ def register_unwinder(locus, unwinder, replace=False):
         i += 1
     locus.frame_unwinders.insert(0, unwinder)
     gdb.invalidate_cached_frames()
+
+class UnwindRegisterValueDemangler(object):
+    """Base class (or a template) for unwind register value demanglers written
+    in Python.
+    """
+    def __init__(self, name):
+        """Constructor.
+
+        Arguments:
+            name: An identifying name for the demangler.
+        """
+        self.name = name
+
+        if gdb.unwind_register_value_demangler is not None:
+            gdb.write (
+                "Deregistering current unwind register value demangler '%s'\n"
+                % gdb.unwind_register_value_demangler.name)
+        gdb.write("Registering unwind register value demangler '%s'\n"
+                  % self.name)
+        gdb.unwind_register_value_demangler = self
+
+    def __call__(self, frame_type, reg, value):
+        """GDB calls this method to demangle a register unwind value.
+
+        Arguments:
+            frame_type: integer constant corresponding to one of the named
+                        constants returned by gdb.Frame.type ().
+            reg: gdb.RegisterDescriptor instance.
+            value: gdb.Value instance.
+
+        Returns:
+            gdb.Value instance if value changed, otherwise None.
+        """
+        raise NotImplementedError("UnwindRegisterValueDemangler __call__.")
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index 78a806c0962..a345c3e357f 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -145,7 +145,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)
 {
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index ab34971ef91..9c81e8cc003 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -30,6 +30,7 @@
 #include "stack.h"
 #include "charset.h"
 #include "block.h"
+#include "value.h"
 
 
 /* Debugging of Python unwinders.  */
@@ -1181,3 +1182,82 @@ PyTypeObject unwind_info_object_type =
   0,                              /* tp_init */
   0,                              /* tp_alloc */
 };
+
+/* Call demangler 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)
+    return EXT_LANG_RC_ERROR;
+
+  gdbarch *gdbarch = get_frame_arch (frame);
+  gdbpy_enter enter_py (gdbarch);
+
+  gdbpy_ref<> py_frame_type
+    = gdb_py_object_from_longest (get_frame_type (frame));
+
+  gdbpy_ref<> py_reg_obj (gdbpy_get_register_descriptor (gdbarch, regnum));
+  if (py_reg_obj == NULL)
+    {
+      gdbpy_print_stack ();
+      return EXT_LANG_RC_ERROR;
+    }
+
+  gdbpy_ref<> py_value_obj (value_to_value_object (*val));
+  if (py_value_obj == NULL)
+    {
+      gdbpy_print_stack ();
+      return EXT_LANG_RC_ERROR;
+    }
+
+  /* Call demangler.  */
+  const char *func_name = "_execute_unwind_register_value_demangler";
+  if (gdb_python_module == NULL
+      || ! PyObject_HasAttrString (gdb_python_module, func_name))
+    {
+      PyErr_SetString (PyExc_NameError,
+	"Installation error: gdb._execute_unwind_register_value_demangler "
+	"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_type.get (),
+				   py_reg_obj.get (), py_value_obj.get (),
+				   NULL));
+  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 (_("An unwind register value demangler should return a gdb.Value "
+	       "instance if the value is changed, otherwise None"));
+      return EXT_LANG_RC_ERROR;
+    }
+  /* Finally update val to the demangled value.  */
+  *val = new_val;
+
+  return EXT_LANG_RC_OK;
+}
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index 3855bdecc16..e21982f7a12 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -1382,6 +1382,27 @@ valpy_fetch_lazy (PyObject *self, PyObject *args)
   Py_RETURN_NONE;
 }
 
+/* Implements gdb.Value.is_lval_register ().  */
+static PyObject *
+valpy_get_is_lval_register (PyObject *self, void *closure)
+{
+  struct value *value = ((value_object *) self)->value;
+  if (value->lval () == lval_register)
+    Py_RETURN_TRUE;
+
+  Py_RETURN_FALSE;
+}
+
+static PyObject *
+valpy_get_is_lval_memory (PyObject *self, void *closure)
+{
+  struct value *value = ((value_object *) self)->value;
+  if (value->lval () == lval_memory)
+    Py_RETURN_TRUE;
+
+  Py_RETURN_FALSE;
+}
+
 /* Calculate and return the address of the PyObject as the value of
    the builtin __hash__ call.  */
 static Py_hash_t
@@ -2202,6 +2223,10 @@ 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 },
+  { "is_lval_register", valpy_get_is_lval_register, NULL,
+    "Boolean telling whether the value is lval register", NULL },
+  { "is_lval_memory", valpy_get_is_lval_memory, NULL,
+    "Boolean telling whether the value is lval memory", NULL },
   { "bytes", valpy_get_bytes, valpy_set_bytes,
     "Return a bytearray containing the bytes of this value.", nullptr },
   {NULL}  /* Sentinel */
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 5262d767529..abe5b525d2e 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -545,9 +545,13 @@ PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
 PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
+extern enum ext_lang_rc gdbpy_demangle_unwind_register_value
+  (frame_info_ptr frame, int regnum, struct value **val);
+
 PyObject *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);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 7e28eb6ebee..2ec16774a37 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -187,7 +187,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 */
diff --git a/gdb/testsuite/gdb.python/py-unwind-demangler.c b/gdb/testsuite/gdb.python/py-unwind-demangler.c
new file mode 100644
index 00000000000..6b7cd50c2dd
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-demangler.c
@@ -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-demangler 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;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-demangler.exp b/gdb/testsuite/gdb.python/py-unwind-demangler.exp
new file mode 100644
index 00000000000..0361f00e34f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-demangler.exp
@@ -0,0 +1,86 @@
+# 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 unwind value
+# demangler support.
+
+# The demangler 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 demangler 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 demangler 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 demangler
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_test "source ${pyfile}" \
+    "Registering unwind register value demangler 'test demangler'" \
+    "import python scripts"
+
+# Flush the register cache (which also flushes the frame cache) so we can get a
+# full backtrace again.
+gdb_test "maint flush register-cache" "Register cache flushed\\." ""
+
+# 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 demangler 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 supported.  Check that it
+# fails gracefully.
+gdb_test "p \$pc = 0x12345678" "Attempt to assign to an unmodifiable value." \
+    "error writing back to demangled register"
diff --git a/gdb/testsuite/gdb.python/py-unwind-demangler.py b/gdb/testsuite/gdb.python/py-unwind-demangler.py
new file mode 100644
index 00000000000..0aca4bebad2
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-demangler.py
@@ -0,0 +1,44 @@
+# 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 UnwindRegisterValueDemangler
+
+class TestUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
+
+    def __init__(self):
+        super().__init__("test demangler")
+
+    def demangle(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 __call__(self, frame_type, 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.is_lval_memory):
+                return self.demangle(value)
+
+        return None
+
+test_demangler = TestUnwindRegisterValueDemangler()
diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
index 93985a99a0c..94c5e9413d0 100644
--- a/gdb/testsuite/gdb.python/py-value.exp
+++ b/gdb/testsuite/gdb.python/py-value.exp
@@ -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 is_lval_register attribute
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('\$pc').is_lval_register)" "= True" "test is_lval_register attribute true"
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('*(int *)\$sp').is_lval_register)" "= False" "test is_lval_register attribute false"
+
+  # Test is_lval_memory attribute
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('\$pc').is_lval_memory)" "= False" "test is_lval_memory attribute false"
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('*(int *)\$sp').is_lval_memory)" "= True" "test is_lval_memory attribute true"
+
   # Test address attribute
   gdb_test "python print ('result = %s' % arg0.address)" "= 0x\[\[:xdigit:\]\]+" "test address attribute"
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH] gdb: Add python support for demangling register unwind values
  2025-04-10 11:04 [RFC PATCH] gdb: Add python support for demangling register unwind values Craig Blackmore
@ 2025-04-10 12:31 ` Eli Zaretskii
  2025-05-08 10:17 ` [RFC PATCH v2] " Craig Blackmore
  1 sibling, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2025-04-10 12:31 UTC (permalink / raw)
  To: Craig Blackmore; +Cc: gdb-patches

> From: Craig Blackmore <craig.blackmore@embecosm.com>
> Cc: Craig Blackmore <craig.blackmore@embecosm.com>
> Date: Thu, 10 Apr 2025 12:04:26 +0100
> 
> This patch addresses the scenario where a register value is saved in
> some mangled form on the target and gdb does not have sufficient
> information to demangle it.
> 
> After getting a register value from an unwinder, call out to extension
> languages to allow the value to be demangled.  This avoids having to
> write a new unwinder and allows the standard unwinders to be used.
> 
> This approach is purely for demangling values that have been read, there
> is no similar support provided for mangling values being written back to
> the target as this was not something that I needed to be able to do.
> 
> Only one demangler can be registered at any one time.  In future, the
> implementation could be extended to support multiple demanglers executed
> in order of priority.
> 
> The demangler is registered globally.  In future, the implementation
> could be extended to register per program space and object file too.
> 
> gdbpy_get_register_descriptor is now externally visible as it is used
> to create a gdb.RegisterDescriptor object to pass to the demangler.
> 
> This patch adds gdb.Value.is_lval_{register,memory} variables so that
> the demangler can choose to skip modifying values that did not come
> directly from the target and may not need demangling, for example,
> values taken directly from DWARF.
> ---
>  gdb/NEWS                                      | 11 +++
>  gdb/doc/python.texi                           | 48 +++++++++++
>  gdb/extension-priv.h                          |  8 ++
>  gdb/extension.c                               | 35 ++++++++
>  gdb/extension.h                               |  6 ++
>  gdb/frame.c                                   |  3 +
>  gdb/guile/guile.c                             |  2 +
>  gdb/python/lib/gdb/__init__.py                | 17 ++++
>  gdb/python/lib/gdb/unwinder.py                | 34 ++++++++
>  gdb/python/py-registers.c                     |  2 +-
>  gdb/python/py-unwind.c                        | 80 +++++++++++++++++
>  gdb/python/py-value.c                         | 25 ++++++
>  gdb/python/python-internal.h                  |  4 +
>  gdb/python/python.c                           |  4 +-
>  .../gdb.python/py-unwind-demangler.c          | 80 +++++++++++++++++
>  .../gdb.python/py-unwind-demangler.exp        | 86 +++++++++++++++++++
>  .../gdb.python/py-unwind-demangler.py         | 44 ++++++++++
>  gdb/testsuite/gdb.python/py-value.exp         |  8 ++
>  18 files changed, 495 insertions(+), 2 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.c
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.py

Thanks.  The documentation parts are okay, with one comment:

> +@subheading Unwinder Register Value Demangler Skeleton Code

Making a construct state out of more than 2-3 nouns produces very
confusing phrases in English.  I suggest to use this instead:

  @subheading Skeleton Code for a Register Value Demangler

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH v2] gdb: Add python support for demangling register unwind values
  2025-04-10 11:04 [RFC PATCH] gdb: Add python support for demangling register unwind values Craig Blackmore
  2025-04-10 12:31 ` Eli Zaretskii
@ 2025-05-08 10:17 ` Craig Blackmore
  2025-05-08 11:40   ` Eli Zaretskii
  2025-05-12 13:15   ` Andrew Burgess
  1 sibling, 2 replies; 18+ messages in thread
From: Craig Blackmore @ 2025-05-08 10:17 UTC (permalink / raw)
  To: gdb-patches; +Cc: Craig Blackmore, Eli Zaretskii

This patch addresses the scenario where a register value is saved in
some mangled form on the target and gdb does not have sufficient
information to demangle it.

After getting a register value from an unwinder, call out to extension
languages to allow the value to be demangled.  This avoids having to
write a new unwinder and allows the standard unwinders to be used.

This approach is purely for demangling values that have been read, there
is no similar support provided for mangling values being written back to
the target as this was not something that I needed to be able to do.

Only one demangler can be registered at any one time.  In future, the
implementation could be extended to support multiple demanglers executed
in order of priority.

The demangler is registered globally.  In future, the implementation
could be extended to register per program space and object file too.

gdbpy_get_register_descriptor is now externally visible as it is used
to create a gdb.RegisterDescriptor object to pass to the demangler.

This patch adds gdb.Value.is_lval_{register,memory} variables so that
the demangler can choose to skip modifying values that did not come
directly from the target and may not need demangling, for example,
values taken directly from DWARF.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
Changes since v1:
* Updated subheading in documentation as suggested by Eli.
* Rebased.

 gdb/NEWS                                      | 11 +++
 gdb/doc/python.texi                           | 48 +++++++++++
 gdb/extension-priv.h                          |  8 ++
 gdb/extension.c                               | 35 ++++++++
 gdb/extension.h                               |  6 ++
 gdb/frame.c                                   |  3 +
 gdb/guile/guile.c                             |  2 +
 gdb/python/lib/gdb/__init__.py                | 17 ++++
 gdb/python/lib/gdb/unwinder.py                | 34 ++++++++
 gdb/python/py-registers.c                     |  2 +-
 gdb/python/py-unwind.c                        | 80 +++++++++++++++++
 gdb/python/py-value.c                         | 25 ++++++
 gdb/python/python-internal.h                  |  4 +
 gdb/python/python.c                           |  4 +-
 .../gdb.python/py-unwind-demangler.c          | 80 +++++++++++++++++
 .../gdb.python/py-unwind-demangler.exp        | 86 +++++++++++++++++++
 .../gdb.python/py-unwind-demangler.py         | 44 ++++++++++
 gdb/testsuite/gdb.python/py-value.exp         |  8 ++
 18 files changed, 495 insertions(+), 2 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.py

diff --git a/gdb/NEWS b/gdb/NEWS
index a82b7e3342c..7514ce4ddd9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -112,6 +112,17 @@ info sharedlibrary
 
 * Python API
 
+  ** Added gdb.UnwindRegisterValueDemangler.  This is the base class for Unwind
+     Register Value Demanglers which are called after obtaining a register value
+     from an unwinder and allow that value to be demangled before being used by
+     GDB.
+
+  ** New read-only boolean attribute gdb.Value.is_lval_register which indicates
+     if the value is from a register on the inferior.
+
+  ** New read-only boolean attribute gdb.Value.is_lval_memory which indicates
+     if the value is from inferior memory.
+
   ** New class gdb.Color for dealing with colors.
 
   ** New constant gdb.PARAM_COLOR represents color type of a
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 7bb650347f7..7fd0ba4f83b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -938,6 +938,16 @@ fetched when the value is needed, or when the @code{fetch_lazy}
 method is invoked.  
 @end defvar
 
+@defvar Value.is_lval_register
+The value of this read-only boolean attribute is @code{True} if this
+@code{gdb.Value} is from a register on the inferior.
+@end defvar
+
+@defvar Value.is_lval_memory
+The value of this read-only boolean attribute is @code{True} if this
+@code{gdb.Value} is from inferior memory.
+@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
@@ -3160,6 +3170,44 @@ the matching unwinders are enabled.  The @code{enabled} field of each
 matching unwinder is set to @code{True}.
 @end table
 
+@subheading Unwind Register Value Demangler
+
+After getting a register value from an unwinder, @value{GDBN} will call
+out to extension language demanglers to allow them to modify 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.
+
+Currently, only one demangler can be registered at any one time and it
+is registered globally.
+
+@subheading Skeleton Code for a Register Value Demangler
+
+Here is an example of how to structure a user created demangler:
+
+@smallexample
+from gdb.unwinder import UnwindRegisterValueDemangler
+
+class MyUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
+    def __init__(self):
+        # Register self as the one and only demangler
+        super().__init__("MyUnwinder_Name")
+
+    def __call__(self, frame_type, reg, value):
+        """
+        Return new value if demangled, otherwise None.
+        """
+        if frame_type != <frame we need to handle>:
+          return None
+
+        if reg.name == <reg that needs demangling>:
+          new_value = ... compute demangled value ...
+          return new_value
+
+        return None
+
+my_demangler = MyUnwindRegisterValueDemangler()
+@end smallexample
+
 @node Xmethods In Python
 @subsubsection Xmethods In Python
 @cindex xmethods in Python
diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index cde64a2ccd0..ded61b2b789 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -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.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index 6d14a201a4b..f4dbf0c2b33 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -1102,6 +1102,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 == NULL)
+	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;
+}
+
 void _initialize_extension ();
 void
 _initialize_extension ()
diff --git a/gdb/extension.h b/gdb/extension.h
index c607df89f4d..bfaff6f22ab 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -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) ();
diff --git a/gdb/frame.c b/gdb/frame.c
index fe5336f2401..1824a5ea83e 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1293,6 +1293,9 @@ 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.  */
+  value = ext_lang_demangle_unwind_register_value (next_frame, regnum, value);
+
   if (frame_debug)
     {
       string_file debug_file;
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index 1803df0d3c4..686171e2d63 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -137,6 +137,8 @@ static const struct extension_language_ops guile_extension_ops =
   NULL, /* gdbscm_get_matching_xmethod_workers */
   NULL, /* gdbscm_colorize */
   NULL, /* gdbscm_print_insn */
+
+  NULL, /* gdbscm_demangle_unwind_register_value */
 };
 #endif
 
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 4ea5d06a3ba..9f8c2655c52 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -90,6 +90,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 = []
+# Default unwind register demangler does nothing.
+unwind_register_value_demangler = None
 
 
 def _execute_unwinders(pending_frame):
@@ -130,6 +132,21 @@ def _execute_unwinders(pending_frame):
 
     return None
 
+def _execute_unwind_register_value_demangler(frame_type, reg, value):
+    """Internal function called from GDB to execute demangler.
+
+    Arguments:
+        frame_type: integer constant corresponding to one of the named
+                    constants returned by gdb.Frame.type ().
+        reg: gdb.RegisterDescriptor instance.
+        value: gdb.Value instance.
+
+    Returns:
+        gdb.Value instance if value changed, otherwise None.
+    """
+    if unwind_register_value_demangler is None:
+        return None
+    return unwind_register_value_demangler(frame_type, reg, value)
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
index 3e1f756f92f..78aa1f85b6a 100644
--- a/gdb/python/lib/gdb/unwinder.py
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -138,3 +138,37 @@ def register_unwinder(locus, unwinder, replace=False):
         i += 1
     locus.frame_unwinders.insert(0, unwinder)
     gdb.invalidate_cached_frames()
+
+class UnwindRegisterValueDemangler(object):
+    """Base class (or a template) for unwind register value demanglers written
+    in Python.
+    """
+    def __init__(self, name):
+        """Constructor.
+
+        Arguments:
+            name: An identifying name for the demangler.
+        """
+        self.name = name
+
+        if gdb.unwind_register_value_demangler is not None:
+            gdb.write (
+                "Deregistering current unwind register value demangler '%s'\n"
+                % gdb.unwind_register_value_demangler.name)
+        gdb.write("Registering unwind register value demangler '%s'\n"
+                  % self.name)
+        gdb.unwind_register_value_demangler = self
+
+    def __call__(self, frame_type, reg, value):
+        """GDB calls this method to demangle a register unwind value.
+
+        Arguments:
+            frame_type: integer constant corresponding to one of the named
+                        constants returned by gdb.Frame.type ().
+            reg: gdb.RegisterDescriptor instance.
+            value: gdb.Value instance.
+
+        Returns:
+            gdb.Value instance if value changed, otherwise None.
+        """
+        raise NotImplementedError("UnwindRegisterValueDemangler __call__.")
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index 9be2e3ceaf5..79a522bde0a 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -145,7 +145,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)
 {
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index d43d7e97d99..6ec239664f5 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -30,6 +30,7 @@
 #include "stack.h"
 #include "charset.h"
 #include "block.h"
+#include "value.h"
 
 
 /* Debugging of Python unwinders.  */
@@ -1181,3 +1182,82 @@ PyTypeObject unwind_info_object_type =
   0,                              /* tp_init */
   0,                              /* tp_alloc */
 };
+
+/* Call demangler 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)
+    return EXT_LANG_RC_ERROR;
+
+  gdbarch *gdbarch = get_frame_arch (frame);
+  gdbpy_enter enter_py (gdbarch);
+
+  gdbpy_ref<> py_frame_type
+    = gdb_py_object_from_longest (get_frame_type (frame));
+
+  gdbpy_ref<> py_reg_obj (gdbpy_get_register_descriptor (gdbarch, regnum));
+  if (py_reg_obj == NULL)
+    {
+      gdbpy_print_stack ();
+      return EXT_LANG_RC_ERROR;
+    }
+
+  gdbpy_ref<> py_value_obj (value_to_value_object (*val));
+  if (py_value_obj == NULL)
+    {
+      gdbpy_print_stack ();
+      return EXT_LANG_RC_ERROR;
+    }
+
+  /* Call demangler.  */
+  const char *func_name = "_execute_unwind_register_value_demangler";
+  if (gdb_python_module == NULL
+      || ! PyObject_HasAttrString (gdb_python_module, func_name))
+    {
+      PyErr_SetString (PyExc_NameError,
+	"Installation error: gdb._execute_unwind_register_value_demangler "
+	"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_type.get (),
+				   py_reg_obj.get (), py_value_obj.get (),
+				   NULL));
+  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 (_("An unwind register value demangler should return a gdb.Value "
+	       "instance if the value is changed, otherwise None"));
+      return EXT_LANG_RC_ERROR;
+    }
+  /* Finally update val to the demangled value.  */
+  *val = new_val;
+
+  return EXT_LANG_RC_OK;
+}
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index 8a2e26368d7..bb49b245abc 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -1381,6 +1381,27 @@ valpy_fetch_lazy (PyObject *self, PyObject *args)
   Py_RETURN_NONE;
 }
 
+/* Implements gdb.Value.is_lval_register ().  */
+static PyObject *
+valpy_get_is_lval_register (PyObject *self, void *closure)
+{
+  struct value *value = ((value_object *) self)->value;
+  if (value->lval () == lval_register)
+    Py_RETURN_TRUE;
+
+  Py_RETURN_FALSE;
+}
+
+static PyObject *
+valpy_get_is_lval_memory (PyObject *self, void *closure)
+{
+  struct value *value = ((value_object *) self)->value;
+  if (value->lval () == lval_memory)
+    Py_RETURN_TRUE;
+
+  Py_RETURN_FALSE;
+}
+
 /* Calculate and return the address of the PyObject as the value of
    the builtin __hash__ call.  */
 static Py_hash_t
@@ -2201,6 +2222,10 @@ 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 },
+  { "is_lval_register", valpy_get_is_lval_register, NULL,
+    "Boolean telling whether the value is lval register", NULL },
+  { "is_lval_memory", valpy_get_is_lval_memory, NULL,
+    "Boolean telling whether the value is lval memory", NULL },
   { "bytes", valpy_get_bytes, valpy_set_bytes,
     "Return a bytearray containing the bytes of this value.", nullptr },
   {NULL}  /* Sentinel */
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 5262d767529..abe5b525d2e 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -545,9 +545,13 @@ PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
 PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
+extern enum ext_lang_rc gdbpy_demangle_unwind_register_value
+  (frame_info_ptr frame, int regnum, struct value **val);
+
 PyObject *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);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 24cb511c3d5..0d5001aae95 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -187,7 +187,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 */
diff --git a/gdb/testsuite/gdb.python/py-unwind-demangler.c b/gdb/testsuite/gdb.python/py-unwind-demangler.c
new file mode 100644
index 00000000000..6b7cd50c2dd
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-demangler.c
@@ -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-demangler 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;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-demangler.exp b/gdb/testsuite/gdb.python/py-unwind-demangler.exp
new file mode 100644
index 00000000000..0361f00e34f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-demangler.exp
@@ -0,0 +1,86 @@
+# 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 unwind value
+# demangler support.
+
+# The demangler 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 demangler 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 demangler 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 demangler
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_test "source ${pyfile}" \
+    "Registering unwind register value demangler 'test demangler'" \
+    "import python scripts"
+
+# Flush the register cache (which also flushes the frame cache) so we can get a
+# full backtrace again.
+gdb_test "maint flush register-cache" "Register cache flushed\\." ""
+
+# 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 demangler 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 supported.  Check that it
+# fails gracefully.
+gdb_test "p \$pc = 0x12345678" "Attempt to assign to an unmodifiable value." \
+    "error writing back to demangled register"
diff --git a/gdb/testsuite/gdb.python/py-unwind-demangler.py b/gdb/testsuite/gdb.python/py-unwind-demangler.py
new file mode 100644
index 00000000000..0aca4bebad2
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-demangler.py
@@ -0,0 +1,44 @@
+# 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 UnwindRegisterValueDemangler
+
+class TestUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
+
+    def __init__(self):
+        super().__init__("test demangler")
+
+    def demangle(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 __call__(self, frame_type, 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.is_lval_memory):
+                return self.demangle(value)
+
+        return None
+
+test_demangler = TestUnwindRegisterValueDemangler()
diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
index 93985a99a0c..94c5e9413d0 100644
--- a/gdb/testsuite/gdb.python/py-value.exp
+++ b/gdb/testsuite/gdb.python/py-value.exp
@@ -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 is_lval_register attribute
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('\$pc').is_lval_register)" "= True" "test is_lval_register attribute true"
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('*(int *)\$sp').is_lval_register)" "= False" "test is_lval_register attribute false"
+
+  # Test is_lval_memory attribute
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('\$pc').is_lval_memory)" "= False" "test is_lval_memory attribute false"
+  gdb_test "python print ('result = %s' % gdb.parse_and_eval ('*(int *)\$sp').is_lval_memory)" "= True" "test is_lval_memory attribute true"
+
   # Test address attribute
   gdb_test "python print ('result = %s' % arg0.address)" "= 0x\[\[:xdigit:\]\]+" "test address attribute"
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH v2] gdb: Add python support for demangling register unwind values
  2025-05-08 10:17 ` [RFC PATCH v2] " Craig Blackmore
@ 2025-05-08 11:40   ` Eli Zaretskii
  2025-05-12 13:15   ` Andrew Burgess
  1 sibling, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2025-05-08 11:40 UTC (permalink / raw)
  To: Craig Blackmore; +Cc: gdb-patches

> From: Craig Blackmore <craig.blackmore@embecosm.com>
> Cc: Craig Blackmore <craig.blackmore@embecosm.com>,
> 	Eli Zaretskii <eliz@gnu.org>
> Date: Thu,  8 May 2025 11:17:21 +0100
> 
> 
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> Changes since v1:
> * Updated subheading in documentation as suggested by Eli.
> * Rebased.
> 
>  gdb/NEWS                                      | 11 +++
>  gdb/doc/python.texi                           | 48 +++++++++++
>  gdb/extension-priv.h                          |  8 ++
>  gdb/extension.c                               | 35 ++++++++
>  gdb/extension.h                               |  6 ++
>  gdb/frame.c                                   |  3 +
>  gdb/guile/guile.c                             |  2 +
>  gdb/python/lib/gdb/__init__.py                | 17 ++++
>  gdb/python/lib/gdb/unwinder.py                | 34 ++++++++
>  gdb/python/py-registers.c                     |  2 +-
>  gdb/python/py-unwind.c                        | 80 +++++++++++++++++
>  gdb/python/py-value.c                         | 25 ++++++
>  gdb/python/python-internal.h                  |  4 +
>  gdb/python/python.c                           |  4 +-
>  .../gdb.python/py-unwind-demangler.c          | 80 +++++++++++++++++
>  .../gdb.python/py-unwind-demangler.exp        | 86 +++++++++++++++++++
>  .../gdb.python/py-unwind-demangler.py         | 44 ++++++++++
>  gdb/testsuite/gdb.python/py-value.exp         |  8 ++
>  18 files changed, 495 insertions(+), 2 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.c
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-demangler.py
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index a82b7e3342c..7514ce4ddd9 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -112,6 +112,17 @@ info sharedlibrary
>  
>  * Python API
>  
> +  ** Added gdb.UnwindRegisterValueDemangler.  This is the base class for Unwind
> +     Register Value Demanglers which are called after obtaining a register value
> +     from an unwinder and allow that value to be demangled before being used by
> +     GDB.
> +
> +  ** New read-only boolean attribute gdb.Value.is_lval_register which indicates
> +     if the value is from a register on the inferior.
> +
> +  ** New read-only boolean attribute gdb.Value.is_lval_memory which indicates
> +     if the value is from inferior memory.
> +
>    ** New class gdb.Color for dealing with colors.
>  
>    ** New constant gdb.PARAM_COLOR represents color type of a
> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
> index 7bb650347f7..7fd0ba4f83b 100644
> --- a/gdb/doc/python.texi
> +++ b/gdb/doc/python.texi
> @@ -938,6 +938,16 @@ fetched when the value is needed, or when the @code{fetch_lazy}
>  method is invoked.  
>  @end defvar
>  
> +@defvar Value.is_lval_register
> +The value of this read-only boolean attribute is @code{True} if this
> +@code{gdb.Value} is from a register on the inferior.
> +@end defvar
> +
> +@defvar Value.is_lval_memory
> +The value of this read-only boolean attribute is @code{True} if this
> +@code{gdb.Value} is from inferior memory.
> +@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
> @@ -3160,6 +3170,44 @@ the matching unwinders are enabled.  The @code{enabled} field of each
>  matching unwinder is set to @code{True}.
>  @end table
>  
> +@subheading Unwind Register Value Demangler
> +
> +After getting a register value from an unwinder, @value{GDBN} will call
> +out to extension language demanglers to allow them to modify 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.
> +
> +Currently, only one demangler can be registered at any one time and it
> +is registered globally.
> +
> +@subheading Skeleton Code for a Register Value Demangler
> +
> +Here is an example of how to structure a user created demangler:
> +
> +@smallexample
> +from gdb.unwinder import UnwindRegisterValueDemangler
> +
> +class MyUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
> +    def __init__(self):
> +        # Register self as the one and only demangler
> +        super().__init__("MyUnwinder_Name")
> +
> +    def __call__(self, frame_type, reg, value):
> +        """
> +        Return new value if demangled, otherwise None.
> +        """
> +        if frame_type != <frame we need to handle>:
> +          return None
> +
> +        if reg.name == <reg that needs demangling>:
> +          new_value = ... compute demangled value ...
> +          return new_value
> +
> +        return None
> +
> +my_demangler = MyUnwindRegisterValueDemangler()
> +@end smallexample
> +
>  @node Xmethods In Python
>  @subsubsection Xmethods In Python
>  @cindex xmethods in Python

Thanks, the documentation parts are okay.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH v2] gdb: Add python support for demangling register unwind values
  2025-05-08 10:17 ` [RFC PATCH v2] " Craig Blackmore
  2025-05-08 11:40   ` Eli Zaretskii
@ 2025-05-12 13:15   ` Andrew Burgess
  2026-04-30 15:19     ` Craig Blackmore
  2026-04-30 15:31     ` [PATCH v3] " Craig Blackmore
  1 sibling, 2 replies; 18+ messages in thread
From: Andrew Burgess @ 2025-05-12 13:15 UTC (permalink / raw)
  To: Craig Blackmore, gdb-patches; +Cc: Craig Blackmore, Eli Zaretskii

Craig Blackmore <craig.blackmore@embecosm.com> writes:

> This patch addresses the scenario where a register value is saved in
> some mangled form on the target and gdb does not have sufficient
> information to demangle it.

I'm assuming this is some kind of security related thing, e.g. return
address is hashed or "encrypted" in some way?  Some of my thoughts below
are based on this assumption.

>
> After getting a register value from an unwinder, call out to extension
> languages to allow the value to be demangled.  This avoids having to
> write a new unwinder and allows the standard unwinders to be used.
>
> This approach is purely for demangling values that have been read, there
> is no similar support provided for mangling values being written back to
> the target as this was not something that I needed to be able to do.

OK, but, presumably, if you did try to modify one of these demangled
registers, and then resumed the inferior, things would not continue as
expected?

If this is true, and right now you don't need to write the value back.
Would it not be worth designing an API now that _could_ support this
functionality?

I have no problem, in theory, with having this functionality in the API,
the problem is, once things are added to the Python API, we're usually
pretty conservative about changing them, so we can only add new
features.

Now, sure, if, in the future, we want to support writing registers, we
can add a whole new, parallel, API that supports the writing case.  But
wouldn't it be nice if we spent a little time thinking, up front, what
that writing case might look like, even if it's not implemented?  Maybe
we can just document in this commit message what that support would look
like, and how it would fit into the existing API.

>
> Only one demangler can be registered at any one time.  In future, the
> implementation could be extended to support multiple demanglers executed
> in order of priority.

Again, have you thought about how this might look?  For the same reasons
as before, while for changes to GDB's C++ it's fine to do "just enough",
the Python API has a nasty habit of becoming locked in.

I notice, for example, that register demanglers are created and
registered by creating a UnwindRegisterValueDemangler instance.  In
contrast, instruct, many other similar functionality is done via a
gdb.register_* or gdb.*.register_ function which take a locus that
allows handlers to be attached to specific objfiles or program spaces.

I think it might be a good idea if you switched to using this same
approach.  Currently, these register functions accept a locus of `None`
to indicate that this should be a global registration.  You could, for
example, throw an error if a locus other than `None` is used.  But this
would then leave the door open, in future, to support other locus, which
would allow per-program-space registrations for example.

I also wonder if you should be more inline with the existing register
functions when it comes to supporting multiple handlers, while at the
same time, not actually changing what you support?  For example, other
register functions take a `replace` flag which controls whether existing
handlers are replaced.  You could just support replacing a handler with
the same name, or adding a single handler, anything else: raise an
error.

This would be a little more work up front, but not significantly, and in
the future.  The benefit is the API is consistent, and future proof.
I'm not sure how you'd extend the existing API to handle
per-program-space registrations, which I think should be a minimum
requirement.

>
> The demangler is registered globally.  In future, the implementation
> could be extended to register per program space and object file too.

See above.  Or maybe outline how you'd see that future design being.

>
> gdbpy_get_register_descriptor is now externally visible as it is used
> to create a gdb.RegisterDescriptor object to pass to the demangler.
>
> This patch adds gdb.Value.is_lval_{register,memory} variables so that
> the demangler can choose to skip modifying values that did not come
> directly from the target and may not need demangling, for example,
> values taken directly from DWARF.

I do wonder if this is the right approach for dealing with lval types.
If we ever want to handle different lval types in the future we're now
stuck adding yet more is_lval_* methods.

Wouldn't a better approach be to add a read-only lval type attribute?  I
don't think you'd even need to support all the types necessarily, you
could add constants for maybe just: not_lval, lval_memory,
lval_register, and map anything not caught be the above to not_lval, and
document that other lval types might be added in the future.  Though I'm
happy to be convinced that separate flags is right way to go...

> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
> index 7bb650347f7..7fd0ba4f83b 100644
> --- a/gdb/doc/python.texi
> +++ b/gdb/doc/python.texi
> @@ -938,6 +938,16 @@ fetched when the value is needed, or when the @code{fetch_lazy}
>  method is invoked.  
>  @end defvar
>  
> +@defvar Value.is_lval_register
> +The value of this read-only boolean attribute is @code{True} if this
> +@code{gdb.Value} is from a register on the inferior.
> +@end defvar
> +
> +@defvar Value.is_lval_memory
> +The value of this read-only boolean attribute is @code{True} if this
> +@code{gdb.Value} is from inferior memory.
> +@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
> @@ -3160,6 +3170,44 @@ the matching unwinders are enabled.  The @code{enabled} field of each
>  matching unwinder is set to @code{True}.
>  @end table
>  
> +@subheading Unwind Register Value Demangler
> +
> +After getting a register value from an unwinder, @value{GDBN} will call
> +out to extension language demanglers to allow them to modify 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.
> +
> +Currently, only one demangler can be registered at any one time and it
> +is registered globally.
> +
> +@subheading Skeleton Code for a Register Value Demangler
> +
> +Here is an example of how to structure a user created demangler:
> +
> +@smallexample
> +from gdb.unwinder import UnwindRegisterValueDemangler
> +
> +class MyUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
> +    def __init__(self):
> +        # Register self as the one and only demangler
> +        super().__init__("MyUnwinder_Name")
> +
> +    def __call__(self, frame_type, reg, value):

I'll add this comment here just because: I think it is a mistake to pass
through the `frame_type` here.  I think you'd be better off passing the
gdb.Frame object itself, then let the demangled grab the frame type if
that's what it needs.  This is a pretty small change, but gives the API
greater flexibility for the future.

> +        """
> +        Return new value if demangled, otherwise None.
> +        """
> +        if frame_type != <frame we need to handle>:
> +          return None
> +
> +        if reg.name == <reg that needs demangling>:
> +          new_value = ... compute demangled value ...
> +          return new_value
> +
> +        return None
> +
> +my_demangler = MyUnwindRegisterValueDemangler()
> +@end smallexample
> +
>  @node Xmethods In Python
>  @subsubsection Xmethods In Python
>  @cindex xmethods in Python


> diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
> index d43d7e97d99..6ec239664f5 100644
> --- a/gdb/python/py-unwind.c
> +++ b/gdb/python/py-unwind.c
> @@ -30,6 +30,7 @@
>  #include "stack.h"
>  #include "charset.h"
>  #include "block.h"
> +#include "value.h"
>  
>  
>  /* Debugging of Python unwinders.  */
> @@ -1181,3 +1182,82 @@ PyTypeObject unwind_info_object_type =
>    0,                              /* tp_init */
>    0,                              /* tp_alloc */
>  };
> +
> +/* Call demangler 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)
> +    return EXT_LANG_RC_ERROR;

If you checkout gdbpy_print_insn you'll notice that it uses this:

  if (!gdb_python_initialized || !python_print_insn_enabled)
    return {};

The `python_print_insn_enabled` is a C++ global that is set from Python
code when any handlers are registered.

The point here is that for the common case, where no handler is
registered, GDB doesn't need to enter the Python environment, or create,
access, or setup, any Python state at all.

This is a pretty easy thing to add, but my thinking at the time, is that
this will give a small improvement (of no Python work) for the common
case.  Do you think you could look at how hard it would be to do
something similar in this case too please?


> +
> +  gdbarch *gdbarch = get_frame_arch (frame);
> +  gdbpy_enter enter_py (gdbarch);
> +
> +  gdbpy_ref<> py_frame_type
> +    = gdb_py_object_from_longest (get_frame_type (frame));
> +
> +  gdbpy_ref<> py_reg_obj (gdbpy_get_register_descriptor (gdbarch, regnum));
> +  if (py_reg_obj == NULL)

Remember s/NULL/nullptr/ throughout this patch.

> +    {
> +      gdbpy_print_stack ();
> +      return EXT_LANG_RC_ERROR;
> +    }
> +
> +  gdbpy_ref<> py_value_obj (value_to_value_object (*val));
> +  if (py_value_obj == NULL)
> +    {
> +      gdbpy_print_stack ();
> +      return EXT_LANG_RC_ERROR;
> +    }
> +
> +  /* Call demangler.  */
> +  const char *func_name = "_execute_unwind_register_value_demangler";
> +  if (gdb_python_module == NULL
> +      || ! PyObject_HasAttrString (gdb_python_module, func_name))
> +    {
> +      PyErr_SetString (PyExc_NameError,
> +	"Installation error: gdb._execute_unwind_register_value_demangler "
> +	"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_type.get (),
> +				   py_reg_obj.get (), py_value_obj.get (),
> +				   NULL));
> +  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 (_("An unwind register value demangler should return a gdb.Value "
> +	       "instance if the value is changed, otherwise None"));
> +      return EXT_LANG_RC_ERROR;

There's no need for a return after a call to error().  I also notice
that this error message doesn't show up in the testsuite, so it would
appear that this case isn't being tested maybe?

Thanks,
Andrew

> +    }
> +  /* Finally update val to the demangled value.  */
> +  *val = new_val;
> +
> +  return EXT_LANG_RC_OK;
> +}


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH v2] gdb: Add python support for demangling register unwind values
  2025-05-12 13:15   ` Andrew Burgess
@ 2026-04-30 15:19     ` Craig Blackmore
  2026-04-30 15:31     ` [PATCH v3] " Craig Blackmore
  1 sibling, 0 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-04-30 15:19 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches; +Cc: Eli Zaretskii

Hi Andrew,

Thanks for the review and apologies for the very slow reply as it took 
longer to get back to this than I'd hoped.

I've responded inline with how I've addressed your comments and I will 
post a v3 patch shortly.

On 12/05/2025 14:15, Andrew Burgess wrote:
> Craig Blackmore <craig.blackmore@embecosm.com> writes:
>
>> This patch addresses the scenario where a register value is saved in
>> some mangled form on the target and gdb does not have sufficient
>> information to demangle it.
> I'm assuming this is some kind of security related thing, e.g. return
> address is hashed or "encrypted" in some way?  Some of my thoughts below
> are based on this assumption.
Yes it's security related.
>> After getting a register value from an unwinder, call out to extension
>> languages to allow the value to be demangled.  This avoids having to
>> write a new unwinder and allows the standard unwinders to be used.
>>
>> This approach is purely for demangling values that have been read, there
>> is no similar support provided for mangling values being written back to
>> the target as this was not something that I needed to be able to do.
> OK, but, presumably, if you did try to modify one of these demangled
> registers, and then resumed the inferior, things would not continue as
> expected?
Attempting to modify a demangled register results in an "Attempt to 
assign to an unmodifiable value" error which is checked for in 
py-unwind-demangler.exp.
> If this is true, and right now you don't need to write the value back.
> Would it not be worth designing an API now that _could_ support this
> functionality?
>
> I have no problem, in theory, with having this functionality in the API,
> the problem is, once things are added to the Python API, we're usually
> pretty conservative about changing them, so we can only add new
> features.
>
> Now, sure, if, in the future, we want to support writing registers, we
> can add a whole new, parallel, API that supports the writing case.  But
> wouldn't it be nice if we spent a little time thinking, up front, what
> that writing case might look like, even if it's not implemented?  Maybe
> we can just document in this commit message what that support would look
> like, and how it would fit into the existing API.
I've changed `gdb.unwinder.UnwindRegisterValueDemangler` to 
`gdb.unwinder.UnwindRegisterValueTransformer` which is a base class for 
transformers that provide methods for transforming unwind register 
values.  Instead of calling `__call__` to demangle we now call 
`demangle`.  In future, other transform methods such as `mangle` could 
be added.
>> Only one demangler can be registered at any one time.  In future, the
>> implementation could be extended to support multiple demanglers executed
>> in order of priority.
> Again, have you thought about how this might look?  For the same reasons
> as before, while for changes to GDB's C++ it's fine to do "just enough",
> the Python API has a nasty habit of becoming locked in.
>
> I notice, for example, that register demanglers are created and
> registered by creating a UnwindRegisterValueDemangler instance.  In
> contrast, instruct, many other similar functionality is done via a
> gdb.register_* or gdb.*.register_ function which take a locus that
> allows handlers to be attached to specific objfiles or program spaces.
>
> I think it might be a good idea if you switched to using this same
> approach.  Currently, these register functions accept a locus of `None`
> to indicate that this should be a global registration.  You could, for
> example, throw an error if a locus other than `None` is used.  But this
> would then leave the door open, in future, to support other locus, which
> would allow per-program-space registrations for example.
I have changed the registration to be similar to python unwinder 
registration so that registering multiple transformers and registering 
to other loci can be supported in future.
> I also wonder if you should be more inline with the existing register
> functions when it comes to supporting multiple handlers, while at the
> same time, not actually changing what you support?  For example, other
> register functions take a `replace` flag which controls whether existing
> handlers are replaced.  You could just support replacing a handler with
> the same name, or adding a single handler, anything else: raise an
> error.
Whilst changing the registration approach, I added support for the 
`replace` flag with the behaviour you suggested.
> This would be a little more work up front, but not significantly, and in
> the future.  The benefit is the API is consistent, and future proof.
> I'm not sure how you'd extend the existing API to handle
> per-program-space registrations, which I think should be a minimum
> requirement.
>
>> The demangler is registered globally.  In future, the implementation
>> could be extended to register per program space and object file too.
> See above.  Or maybe outline how you'd see that future design being.
See above.
>> gdbpy_get_register_descriptor is now externally visible as it is used
>> to create a gdb.RegisterDescriptor object to pass to the demangler.
>>
>> This patch adds gdb.Value.is_lval_{register,memory} variables so that
>> the demangler can choose to skip modifying values that did not come
>> directly from the target and may not need demangling, for example,
>> values taken directly from DWARF.
> I do wonder if this is the right approach for dealing with lval types.
> If we ever want to handle different lval types in the future we're now
> stuck adding yet more is_lval_* methods.
>
> Wouldn't a better approach be to add a read-only lval type attribute?  I
> don't think you'd even need to support all the types necessarily, you
> could add constants for maybe just: not_lval, lval_memory,
> lval_register, and map anything not caught be the above to not_lval, and
> document that other lval types might be added in the future.  Though I'm
> happy to be convinced that separate flags is right way to go...
I agree that's a better approach. I've made this change.
>> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
>> index 7bb650347f7..7fd0ba4f83b 100644
>> --- a/gdb/doc/python.texi
>> +++ b/gdb/doc/python.texi
>> @@ -938,6 +938,16 @@ fetched when the value is needed, or when the @code{fetch_lazy}
>>   method is invoked.
>>   @end defvar
>>   
>> +@defvar Value.is_lval_register
>> +The value of this read-only boolean attribute is @code{True} if this
>> +@code{gdb.Value} is from a register on the inferior.
>> +@end defvar
>> +
>> +@defvar Value.is_lval_memory
>> +The value of this read-only boolean attribute is @code{True} if this
>> +@code{gdb.Value} is from inferior memory.
>> +@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
>> @@ -3160,6 +3170,44 @@ the matching unwinders are enabled.  The @code{enabled} field of each
>>   matching unwinder is set to @code{True}.
>>   @end table
>>   
>> +@subheading Unwind Register Value Demangler
>> +
>> +After getting a register value from an unwinder, @value{GDBN} will call
>> +out to extension language demanglers to allow them to modify 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.
>> +
>> +Currently, only one demangler can be registered at any one time and it
>> +is registered globally.
>> +
>> +@subheading Skeleton Code for a Register Value Demangler
>> +
>> +Here is an example of how to structure a user created demangler:
>> +
>> +@smallexample
>> +from gdb.unwinder import UnwindRegisterValueDemangler
>> +
>> +class MyUnwindRegisterValueDemangler(UnwindRegisterValueDemangler):
>> +    def __init__(self):
>> +        # Register self as the one and only demangler
>> +        super().__init__("MyUnwinder_Name")
>> +
>> +    def __call__(self, frame_type, reg, value):
> I'll add this comment here just because: I think it is a mistake to pass
> through the `frame_type` here.  I think you'd be better off passing the
> gdb.Frame object itself, then let the demangled grab the frame type if
> that's what it needs.  This is a pretty small change, but gives the API
> greater flexibility for the future.
Done.
>> +        """
>> +        Return new value if demangled, otherwise None.
>> +        """
>> +        if frame_type != <frame we need to handle>:
>> +          return None
>> +
>> +        if reg.name == <reg that needs demangling>:
>> +          new_value = ... compute demangled value ...
>> +          return new_value
>> +
>> +        return None
>> +
>> +my_demangler = MyUnwindRegisterValueDemangler()
>> +@end smallexample
>> +
>>   @node Xmethods In Python
>>   @subsubsection Xmethods In Python
>>   @cindex xmethods in Python
>
>> diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
>> index d43d7e97d99..6ec239664f5 100644
>> --- a/gdb/python/py-unwind.c
>> +++ b/gdb/python/py-unwind.c
>> @@ -30,6 +30,7 @@
>>   #include "stack.h"
>>   #include "charset.h"
>>   #include "block.h"
>> +#include "value.h"
>>   
>>   
>>   /* Debugging of Python unwinders.  */
>> @@ -1181,3 +1182,82 @@ PyTypeObject unwind_info_object_type =
>>     0,                              /* tp_init */
>>     0,                              /* tp_alloc */
>>   };
>> +
>> +/* Call demangler 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)
>> +    return EXT_LANG_RC_ERROR;
> If you checkout gdbpy_print_insn you'll notice that it uses this:
>
>    if (!gdb_python_initialized || !python_print_insn_enabled)
>      return {};
>
> The `python_print_insn_enabled` is a C++ global that is set from Python
> code when any handlers are registered.
>
> The point here is that for the common case, where no handler is
> registered, GDB doesn't need to enter the Python environment, or create,
> access, or setup, any Python state at all.
>
> This is a pretty easy thing to add, but my thinking at the time, is that
> this will give a small improvement (of no Python work) for the common
> case.  Do you think you could look at how hard it would be to do
> something similar in this case too please?
Done. I've followed a similar approach to how 
`python_print_insn_enabled` is set / used.
>
>> +
>> +  gdbarch *gdbarch = get_frame_arch (frame);
>> +  gdbpy_enter enter_py (gdbarch);
>> +
>> +  gdbpy_ref<> py_frame_type
>> +    = gdb_py_object_from_longest (get_frame_type (frame));
>> +
>> +  gdbpy_ref<> py_reg_obj (gdbpy_get_register_descriptor (gdbarch, regnum));
>> +  if (py_reg_obj == NULL)
> Remember s/NULL/nullptr/ throughout this patch.
Done.
>> +    {
>> +      gdbpy_print_stack ();
>> +      return EXT_LANG_RC_ERROR;
>> +    }
>> +
>> +  gdbpy_ref<> py_value_obj (value_to_value_object (*val));
>> +  if (py_value_obj == NULL)
>> +    {
>> +      gdbpy_print_stack ();
>> +      return EXT_LANG_RC_ERROR;
>> +    }
>> +
>> +  /* Call demangler.  */
>> +  const char *func_name = "_execute_unwind_register_value_demangler";
>> +  if (gdb_python_module == NULL
>> +      || ! PyObject_HasAttrString (gdb_python_module, func_name))
>> +    {
>> +      PyErr_SetString (PyExc_NameError,
>> +	"Installation error: gdb._execute_unwind_register_value_demangler "
>> +	"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_type.get (),
>> +				   py_reg_obj.get (), py_value_obj.get (),
>> +				   NULL));
>> +  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 (_("An unwind register value demangler should return a gdb.Value "
>> +	       "instance if the value is changed, otherwise None"));
>> +      return EXT_LANG_RC_ERROR;
> There's no need for a return after a call to error().  I also notice
> that this error message doesn't show up in the testsuite, so it would
> appear that this case isn't being tested maybe?

I've removed the return and added a testcase.

Thanks,

Craig

> Thanks,
> Andrew
>
>> +    }
>> +  /* Finally update val to the demangled value.  */
>> +  *val = new_val;
>> +
>> +  return EXT_LANG_RC_OK;
>> +}

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v3] gdb: Add python support for demangling register unwind values
  2025-05-12 13:15   ` Andrew Burgess
  2026-04-30 15:19     ` Craig Blackmore
@ 2026-04-30 15:31     ` Craig Blackmore
  2026-04-30 16:05       ` Eli Zaretskii
  2026-05-20 13:16       ` [PATCH v4 0/1] " Craig Blackmore
  1 sibling, 2 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-04-30 15:31 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, eliz, Craig Blackmore

This patch addresses the scenario where a register value is saved in
some mangled form on the target and gdb does not have sufficient
information to demangle it.

After getting a register value from an unwinder, gdb will call out to
extension languages to allow the value to be demangled.  This avoids
having to write a new unwinder and allows the standard unwinders to be
used.

This patch adds the python class
`gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
for transformers which provide methods for transforming unwind register
values.  Currently one such method is supported, `demangle`.  There is
no similar support for mangling values being written back to the target
as this was not something I needed to be able to do.  Such support can
be added to the python API in future by supporting a `mangle` method.
Currently writing back to a demangled register will produce an "Attempt
to assign to an unmodifiable value" error.

Only one transformer can be registered globally at any one time.
Attempting to register more than one transformer or to any other locus
produces an error.  The function for registering transformers takes a
locus to allow other loci to be supported in future.  Registered
transformers are stored in a list so that registering multiple
transformers may be supported in future.

`gdbpy_get_register_descriptor` is now externally visible as it is used
to create a gdb.RegisterDescriptor object to pass to the `demangle`
method.

This patch adds read-only attribute `gdb.Value.lval_type` which
transformer methods can use to decide to skip modifying values that did
not come directly from the target and may not need transforming, for
example, values taken directly from DWARF.

Changes since v2:

* Removed RFC from subject.

* Changed `gdb.unwinder.UnwindRegisterValueDemangler` to
  `gdb.unwinder.UnwindRegisterValueTransformer` so that other transform
  methods could be supported in future rather than being restricted to
  demangling only.  Demangling is now done by calling the `demangle`
  method instead of `__call__`.

* Made transformer registration similar to unwinder registration to
  allow future support for registering multiple transformers and
  registering to other loci.

* Call `invalidate_cached_frames` after registering a transformer.

* Made `gdb.unwinder.UnwindRegisterValueTransformer.name` read-only
  and added type check.

* Pass `gdb.Frame` to demanglers instead of `frame_type`.

* Added global `python_transformer_enabled` to `py-unwind.c` which
  python sets when any transformer is registered.  This allows us to
  skip entering the python environment when demangling and no
  transformers are registered.

* Replaced `gdb.Value.is_lval_register` and `gdb.Value.is_lval_memory`
  read-only boolean attributes with `gdb.Value.lval_type` read-only
  type attribute.

* Removed redundant `return` after call to `error`.

* Replaced `NULL` with `nullptr`.

* Added tests for invalid registration and invalid return value from
  demangler.

* Updated tests and documentation to match the above changes.

* Rebased.
---
 gdb/NEWS                                      |  20 +++
 gdb/doc/python.texi                           |  95 ++++++++++
 gdb/extension-priv.h                          |   8 +
 gdb/extension.c                               |  35 ++++
 gdb/extension.h                               |   6 +
 gdb/frame.c                                   |   9 +
 gdb/guile/guile.c                             |   2 +
 gdb/python/lib/gdb/__init__.py                |  21 +++
 gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
 gdb/python/py-registers.c                     |   2 +-
 gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
 gdb/python/py-value.c                         |  21 +++
 gdb/python/python-internal.h                  |   3 +
 gdb/python/python.c                           |   4 +-
 .../gdb.python/py-unwind-transformer-error.py |  33 ++++
 .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
 .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
 .../gdb.python/py-unwind-transformer.py       |  48 +++++
 gdb/testsuite/gdb.python/py-value.exp         |   8 +
 19 files changed, 765 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

diff --git a/gdb/NEWS b/gdb/NEWS
index e233906153a..c1dc008280e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -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.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 96b46a98091..a9f35ef27f3 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -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
diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index 4ef87415413..deca276d4d6 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -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.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index d8ef8123ab5..82314af1d88 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -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");
diff --git a/gdb/extension.h b/gdb/extension.h
index e351b5b672e..63cbea22c3f 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -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) ();
diff --git a/gdb/frame.c b/gdb/frame.c
index 61d37316c6a..f20b09376e2 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -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;
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index ec5f6d08951..4397faeb435 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -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
 
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 1a4aefcd802..83aba4552fb 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -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__))
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
index dc456d1987f..37a3db21945 100644
--- a/gdb/python/lib/gdb/unwinder.py
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -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()
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index c6e0748e935..15a72a162c2 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -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)
 {
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index dcf86f7db3d..198a2e4aa74 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -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;
+}
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index a5b2303728c..79b72716d0e 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -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 */
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 37bc37691fe..b8d0e2429b8 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -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);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 502fe682b2c..5837c5afcb8 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -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 */
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer-error.py b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
new file mode 100644
index 00000000000..c926f374f1f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
@@ -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")
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.c b/gdb/testsuite/gdb.python/py-unwind-transformer.c
new file mode 100644
index 00000000000..9ac9672d325
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.exp b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
new file mode 100644
index 00000000000..91922e4a286
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
@@ -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"
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.py b/gdb/testsuite/gdb.python/py-unwind-transformer.py
new file mode 100644
index 00000000000..a113aaea9b2
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.py
@@ -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")
diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
index a384f4139dd..8b534c786ee 100644
--- a/gdb/testsuite/gdb.python/py-value.exp
+++ b/gdb/testsuite/gdb.python/py-value.exp
@@ -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"
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v3] gdb: Add python support for demangling register unwind values
  2026-04-30 15:31     ` [PATCH v3] " Craig Blackmore
@ 2026-04-30 16:05       ` Eli Zaretskii
  2026-05-20 13:16       ` [PATCH v4 0/1] " Craig Blackmore
  1 sibling, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2026-04-30 16:05 UTC (permalink / raw)
  To: Craig Blackmore; +Cc: gdb-patches, aburgess

> From: Craig Blackmore <craig.blackmore@embecosm.com>
> Cc: aburgess@redhat.com,
> 	eliz@gnu.org,
> 	Craig Blackmore <craig.blackmore@embecosm.com>
> Date: Thu, 30 Apr 2026 16:31:55 +0100
> 
>  gdb/NEWS                                      |  20 +++
>  gdb/doc/python.texi                           |  95 ++++++++++
>  gdb/extension-priv.h                          |   8 +
>  gdb/extension.c                               |  35 ++++
>  gdb/extension.h                               |   6 +
>  gdb/frame.c                                   |   9 +
>  gdb/guile/guile.c                             |   2 +
>  gdb/python/lib/gdb/__init__.py                |  21 +++
>  gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
>  gdb/python/py-registers.c                     |   2 +-
>  gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>  gdb/python/py-value.c                         |  21 +++
>  gdb/python/python-internal.h                  |   3 +
>  gdb/python/python.c                           |   4 +-
>  .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>  .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>  .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>  .../gdb.python/py-unwind-transformer.py       |  48 +++++
>  gdb/testsuite/gdb.python/py-value.exp         |   8 +
>  19 files changed, 765 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

Thanks.

> diff --git a/gdb/NEWS b/gdb/NEWS
> index e233906153a..c1dc008280e 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -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

This part is okay.

> +@defvar Value.lval_type
> +The lval_type of this @code{gdb.Value}.  The value of this attribute can

What is "lval_type"?  If it's a known term in Python programming,
please give it the @code markup.  Alternatively, replace with just
"type", a common word.

> +Additional lval_types may be added in future.

Same here.

> +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.

I'd remove the last sentence, it isn't useful in the manual.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v4 0/1] gdb: Add python support for demangling register unwind values
  2026-04-30 15:31     ` [PATCH v3] " Craig Blackmore
  2026-04-30 16:05       ` Eli Zaretskii
@ 2026-05-20 13:16       ` Craig Blackmore
  2026-05-20 13:16         ` [PATCH v4 1/1] " Craig Blackmore
  2026-06-08 12:04         ` [PATCH v5 0/1] " Craig Blackmore
  1 sibling, 2 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-05-20 13:16 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, eliz, Craig Blackmore

This version addresses Eli Zaretskii's review comments from:

  https://sourceware.org/pipermail/gdb-patches/2026-April/227044.html

Changes since v3:

* Change lval_type to lvalue type in NEWS and python documentation.

* Remove line about future support for multiple transformers or
  different loci from python documentation.

Changes since v2:

* Removed RFC from subject.

* Changed `gdb.unwinder.UnwindRegisterValueDemangler` to
  `gdb.unwinder.UnwindRegisterValueTransformer` so that other transform
  methods could be supported in future rather than being restricted to
  demangling only.  Demangling is now done by calling the `demangle`
  method instead of `__call__`.

* Made transformer registration similar to unwinder registration to
  allow future support for registering multiple transformers and
  registering to other loci.

* Call `invalidate_cached_frames` after registering a transformer.

* Made `gdb.unwinder.UnwindRegisterValueTransformer.name` read-only
  and added type check.

* Pass `gdb.Frame` to demanglers instead of `frame_type`.

* Added global `python_transformer_enabled` to `py-unwind.c` which
  python sets when any transformer is registered.  This allows us to
  skip entering the python environment when demangling and no
  transformers are registered.

* Replaced `gdb.Value.is_lval_register` and `gdb.Value.is_lval_memory`
  read-only boolean attributes with `gdb.Value.lval_type` read-only
  type attribute.

* Removed redundant `return` after call to `error`.

* Replaced `NULL` with `nullptr`.

* Added tests for invalid registration and invalid return value from
  demangler.

* Updated tests and documentation to match the above changes.

* Rebased.

Craig Blackmore (1):
  gdb: Add python support for demangling register unwind values

 gdb/NEWS                                      |  20 +++
 gdb/doc/python.texi                           |  93 ++++++++++
 gdb/extension-priv.h                          |   8 +
 gdb/extension.c                               |  35 ++++
 gdb/extension.h                               |   6 +
 gdb/frame.c                                   |   9 +
 gdb/guile/guile.c                             |   2 +
 gdb/python/lib/gdb/__init__.py                |  21 +++
 gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
 gdb/python/py-registers.c                     |   2 +-
 gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
 gdb/python/py-value.c                         |  21 +++
 gdb/python/python-internal.h                  |   3 +
 gdb/python/python.c                           |   4 +-
 .../gdb.python/py-unwind-transformer-error.py |  33 ++++
 .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
 .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
 .../gdb.python/py-unwind-transformer.py       |  48 +++++
 gdb/testsuite/gdb.python/py-value.exp         |   8 +
 19 files changed, 763 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v4 1/1] gdb: Add python support for demangling register unwind values
  2026-05-20 13:16       ` [PATCH v4 0/1] " Craig Blackmore
@ 2026-05-20 13:16         ` Craig Blackmore
  2026-05-20 15:44           ` Eli Zaretskii
  2026-05-21  9:47           ` Andrew Burgess
  2026-06-08 12:04         ` [PATCH v5 0/1] " Craig Blackmore
  1 sibling, 2 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-05-20 13:16 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, eliz, Craig Blackmore

This patch addresses the scenario where a register value is saved in
some mangled form on the target and gdb does not have sufficient
information to demangle it.

After getting a register value from an unwinder, gdb will call out to
extension languages to allow the value to be demangled.  This avoids
having to write a new unwinder and allows the standard unwinders to be
used.

This patch adds the python class
`gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
for transformers which provide methods for transforming unwind register
values.  Currently one such method is supported, `demangle`.  There is
no similar support for mangling values being written back to the target
as this was not something I needed to be able to do.  Such support can
be added to the python API in future by supporting a `mangle` method.
Currently writing back to a demangled register will produce an "Attempt
to assign to an unmodifiable value" error.

Only one transformer can be registered globally at any one time.
Attempting to register more than one transformer or to any other locus
produces an error.  The function for registering transformers takes a
locus to allow other loci to be supported in future.  Registered
transformers are stored in a list so that registering multiple
transformers may be supported in future.

`gdbpy_get_register_descriptor` is now externally visible as it is used
to create a gdb.RegisterDescriptor object to pass to the `demangle`
method.

This patch adds read-only attribute `gdb.Value.lval_type` which
transformer methods can use to decide to skip modifying values that did
not come directly from the target and may not need transforming, for
example, values taken directly from DWARF.
---
 gdb/NEWS                                      |  20 +++
 gdb/doc/python.texi                           |  93 ++++++++++
 gdb/extension-priv.h                          |   8 +
 gdb/extension.c                               |  35 ++++
 gdb/extension.h                               |   6 +
 gdb/frame.c                                   |   9 +
 gdb/guile/guile.c                             |   2 +
 gdb/python/lib/gdb/__init__.py                |  21 +++
 gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
 gdb/python/py-registers.c                     |   2 +-
 gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
 gdb/python/py-value.c                         |  21 +++
 gdb/python/python-internal.h                  |   3 +
 gdb/python/python.c                           |   4 +-
 .../gdb.python/py-unwind-transformer-error.py |  33 ++++
 .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
 .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
 .../gdb.python/py-unwind-transformer.py       |  48 +++++
 gdb/testsuite/gdb.python/py-value.exp         |   8 +
 19 files changed, 763 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

diff --git a/gdb/NEWS b/gdb/NEWS
index e233906153a..085a32c9709 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -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 lvalue type of a value.
+
+  ** New read-only attribute gdb.Value.lval_type which indicates the
+     lvalue 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.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 96b46a98091..f4db846a36c 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -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 lvalue 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 lvalue 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,67 @@ 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.
+
+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
diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index 4ef87415413..deca276d4d6 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -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.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index d8ef8123ab5..82314af1d88 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -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");
diff --git a/gdb/extension.h b/gdb/extension.h
index e351b5b672e..63cbea22c3f 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -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) ();
diff --git a/gdb/frame.c b/gdb/frame.c
index 61d37316c6a..f20b09376e2 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -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;
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index ec5f6d08951..4397faeb435 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -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
 
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 1a4aefcd802..83aba4552fb 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -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__))
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
index dc456d1987f..37a3db21945 100644
--- a/gdb/python/lib/gdb/unwinder.py
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -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()
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index c6e0748e935..15a72a162c2 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -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)
 {
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index dcf86f7db3d..198a2e4aa74 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -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;
+}
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index a5b2303728c..79b72716d0e 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -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 */
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 37bc37691fe..b8d0e2429b8 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -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);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 502fe682b2c..5837c5afcb8 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -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 */
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer-error.py b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
new file mode 100644
index 00000000000..c926f374f1f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
@@ -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")
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.c b/gdb/testsuite/gdb.python/py-unwind-transformer.c
new file mode 100644
index 00000000000..9ac9672d325
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.exp b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
new file mode 100644
index 00000000000..91922e4a286
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
@@ -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"
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.py b/gdb/testsuite/gdb.python/py-unwind-transformer.py
new file mode 100644
index 00000000000..a113aaea9b2
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.py
@@ -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")
diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
index a384f4139dd..8b534c786ee 100644
--- a/gdb/testsuite/gdb.python/py-value.exp
+++ b/gdb/testsuite/gdb.python/py-value.exp
@@ -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"
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 1/1] gdb: Add python support for demangling register unwind values
  2026-05-20 13:16         ` [PATCH v4 1/1] " Craig Blackmore
@ 2026-05-20 15:44           ` Eli Zaretskii
  2026-05-21  9:47           ` Andrew Burgess
  1 sibling, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2026-05-20 15:44 UTC (permalink / raw)
  To: Craig Blackmore; +Cc: gdb-patches, aburgess

> From: Craig Blackmore <craig.blackmore@embecosm.com>
> Cc: aburgess@redhat.com,
> 	eliz@gnu.org,
> 	Craig Blackmore <craig.blackmore@embecosm.com>
> Date: Wed, 20 May 2026 14:16:07 +0100
> 
> This patch addresses the scenario where a register value is saved in
> some mangled form on the target and gdb does not have sufficient
> information to demangle it.
> 
> After getting a register value from an unwinder, gdb will call out to
> extension languages to allow the value to be demangled.  This avoids
> having to write a new unwinder and allows the standard unwinders to be
> used.
> 
> This patch adds the python class
> `gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
> for transformers which provide methods for transforming unwind register
> values.  Currently one such method is supported, `demangle`.  There is
> no similar support for mangling values being written back to the target
> as this was not something I needed to be able to do.  Such support can
> be added to the python API in future by supporting a `mangle` method.
> Currently writing back to a demangled register will produce an "Attempt
> to assign to an unmodifiable value" error.
> 
> Only one transformer can be registered globally at any one time.
> Attempting to register more than one transformer or to any other locus
> produces an error.  The function for registering transformers takes a
> locus to allow other loci to be supported in future.  Registered
> transformers are stored in a list so that registering multiple
> transformers may be supported in future.
> 
> `gdbpy_get_register_descriptor` is now externally visible as it is used
> to create a gdb.RegisterDescriptor object to pass to the `demangle`
> method.
> 
> This patch adds read-only attribute `gdb.Value.lval_type` which
> transformer methods can use to decide to skip modifying values that did
> not come directly from the target and may not need transforming, for
> example, values taken directly from DWARF.
> ---
>  gdb/NEWS                                      |  20 +++
>  gdb/doc/python.texi                           |  93 ++++++++++
>  gdb/extension-priv.h                          |   8 +
>  gdb/extension.c                               |  35 ++++
>  gdb/extension.h                               |   6 +
>  gdb/frame.c                                   |   9 +
>  gdb/guile/guile.c                             |   2 +
>  gdb/python/lib/gdb/__init__.py                |  21 +++
>  gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
>  gdb/python/py-registers.c                     |   2 +-
>  gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>  gdb/python/py-value.c                         |  21 +++
>  gdb/python/python-internal.h                  |   3 +
>  gdb/python/python.c                           |   4 +-
>  .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>  .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>  .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>  .../gdb.python/py-unwind-transformer.py       |  48 +++++
>  gdb/testsuite/gdb.python/py-value.exp         |   8 +
>  19 files changed, 763 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

Thanks, the documentation parts are okay, with one minor comment:

> +gdb.unwinder.register_unwind_register_value_transformer(None, my_transformer, False)

This line is too long, please break it in two.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 1/1] gdb: Add python support for demangling register unwind values
  2026-05-20 13:16         ` [PATCH v4 1/1] " Craig Blackmore
  2026-05-20 15:44           ` Eli Zaretskii
@ 2026-05-21  9:47           ` Andrew Burgess
  2026-05-27 10:49             ` Craig Blackmore
  1 sibling, 1 reply; 18+ messages in thread
From: Andrew Burgess @ 2026-05-21  9:47 UTC (permalink / raw)
  To: Craig Blackmore, gdb-patches; +Cc: eliz, Craig Blackmore

Craig Blackmore <craig.blackmore@embecosm.com> writes:

> This patch addresses the scenario where a register value is saved in
> some mangled form on the target and gdb does not have sufficient
> information to demangle it.
>
> After getting a register value from an unwinder, gdb will call out to
> extension languages to allow the value to be demangled.  This avoids
> having to write a new unwinder and allows the standard unwinders to be
> used.
>
> This patch adds the python class
> `gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
> for transformers which provide methods for transforming unwind register
> values.  Currently one such method is supported, `demangle`.  There is
> no similar support for mangling values being written back to the target
> as this was not something I needed to be able to do.  Such support can
> be added to the python API in future by supporting a `mangle` method.
> Currently writing back to a demangled register will produce an "Attempt
> to assign to an unmodifiable value" error.

I suspect that most cases a single transformer would only have a single
task, so having an growing list of different transformer method names
seems unnecessary.

Why not make UnwindRegisterValueTransformer's callable (add a __call__
method), and have the signature be:

  def __call__(self, frame_id, reg_descriptor, value, read_p):
   # ...

Most of these arguments are as for the current 'demangle', but READ_P
would be true if the value is being read, of FALSE if it's being
written.

An possibly better alternative would be for the transformers to have two
methods 'read' and 'write' like:

  def read(self, frame_id, reg_desc, value):
    # ...

  def write(self, frame_id, reg_desc, value):
    # ...

With the idea being that when a register is being read the read method
is invoked, and when written the write method is invoked, though right
now you don't need the write method, so I'm not sure how you'd document
this.  Maybe you'd document it as TODO in the code, but not actually
document it in the manual?

This would fully move away from the "demangle" concept and focus more on
the transformer concept.

Thoughts?

Thanks,
Andrew



>
> Only one transformer can be registered globally at any one time.
> Attempting to register more than one transformer or to any other locus
> produces an error.  The function for registering transformers takes a
> locus to allow other loci to be supported in future.  Registered
> transformers are stored in a list so that registering multiple
> transformers may be supported in future.
>
> `gdbpy_get_register_descriptor` is now externally visible as it is used
> to create a gdb.RegisterDescriptor object to pass to the `demangle`
> method.
>
> This patch adds read-only attribute `gdb.Value.lval_type` which
> transformer methods can use to decide to skip modifying values that did
> not come directly from the target and may not need transforming, for
> example, values taken directly from DWARF.
> ---
>  gdb/NEWS                                      |  20 +++
>  gdb/doc/python.texi                           |  93 ++++++++++
>  gdb/extension-priv.h                          |   8 +
>  gdb/extension.c                               |  35 ++++
>  gdb/extension.h                               |   6 +
>  gdb/frame.c                                   |   9 +
>  gdb/guile/guile.c                             |   2 +
>  gdb/python/lib/gdb/__init__.py                |  21 +++
>  gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
>  gdb/python/py-registers.c                     |   2 +-
>  gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>  gdb/python/py-value.c                         |  21 +++
>  gdb/python/python-internal.h                  |   3 +
>  gdb/python/python.c                           |   4 +-
>  .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>  .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>  .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>  .../gdb.python/py-unwind-transformer.py       |  48 +++++
>  gdb/testsuite/gdb.python/py-value.exp         |   8 +
>  19 files changed, 763 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index e233906153a..085a32c9709 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -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 lvalue type of a value.
> +
> +  ** New read-only attribute gdb.Value.lval_type which indicates the
> +     lvalue 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.
> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
> index 96b46a98091..f4db846a36c 100644
> --- a/gdb/doc/python.texi
> +++ b/gdb/doc/python.texi
> @@ -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 lvalue 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 lvalue 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,67 @@ 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.
> +
> +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
> diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
> index 4ef87415413..deca276d4d6 100644
> --- a/gdb/extension-priv.h
> +++ b/gdb/extension-priv.h
> @@ -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.  */
> diff --git a/gdb/extension.c b/gdb/extension.c
> index d8ef8123ab5..82314af1d88 100644
> --- a/gdb/extension.c
> +++ b/gdb/extension.c
> @@ -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");
> diff --git a/gdb/extension.h b/gdb/extension.h
> index e351b5b672e..63cbea22c3f 100644
> --- a/gdb/extension.h
> +++ b/gdb/extension.h
> @@ -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) ();
> diff --git a/gdb/frame.c b/gdb/frame.c
> index 61d37316c6a..f20b09376e2 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -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;
> diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
> index ec5f6d08951..4397faeb435 100644
> --- a/gdb/guile/guile.c
> +++ b/gdb/guile/guile.c
> @@ -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
>  
> diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
> index 1a4aefcd802..83aba4552fb 100644
> --- a/gdb/python/lib/gdb/__init__.py
> +++ b/gdb/python/lib/gdb/__init__.py
> @@ -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__))
> diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
> index dc456d1987f..37a3db21945 100644
> --- a/gdb/python/lib/gdb/unwinder.py
> +++ b/gdb/python/lib/gdb/unwinder.py
> @@ -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()
> diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
> index c6e0748e935..15a72a162c2 100644
> --- a/gdb/python/py-registers.c
> +++ b/gdb/python/py-registers.c
> @@ -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)
>  {
> diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
> index dcf86f7db3d..198a2e4aa74 100644
> --- a/gdb/python/py-unwind.c
> +++ b/gdb/python/py-unwind.c
> @@ -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;
> +}
> diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
> index a5b2303728c..79b72716d0e 100644
> --- a/gdb/python/py-value.c
> +++ b/gdb/python/py-value.c
> @@ -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 */
> diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
> index 37bc37691fe..b8d0e2429b8 100644
> --- a/gdb/python/python-internal.h
> +++ b/gdb/python/python-internal.h
> @@ -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);
> diff --git a/gdb/python/python.c b/gdb/python/python.c
> index 502fe682b2c..5837c5afcb8 100644
> --- a/gdb/python/python.c
> +++ b/gdb/python/python.c
> @@ -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 */
> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer-error.py b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
> new file mode 100644
> index 00000000000..c926f374f1f
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
> @@ -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")
> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.c b/gdb/testsuite/gdb.python/py-unwind-transformer.c
> new file mode 100644
> index 00000000000..9ac9672d325
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer.c
> @@ -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;
> +}
> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.exp b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
> new file mode 100644
> index 00000000000..91922e4a286
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
> @@ -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"
> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.py b/gdb/testsuite/gdb.python/py-unwind-transformer.py
> new file mode 100644
> index 00000000000..a113aaea9b2
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer.py
> @@ -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")
> diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
> index a384f4139dd..8b534c786ee 100644
> --- a/gdb/testsuite/gdb.python/py-value.exp
> +++ b/gdb/testsuite/gdb.python/py-value.exp
> @@ -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"
>  
> -- 
> 2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v4 1/1] gdb: Add python support for demangling register unwind values
  2026-05-21  9:47           ` Andrew Burgess
@ 2026-05-27 10:49             ` Craig Blackmore
  0 siblings, 0 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-05-27 10:49 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches; +Cc: eliz


On 21/05/2026 10:47, Andrew Burgess wrote:
> Craig Blackmore <craig.blackmore@embecosm.com> writes:
>
>> This patch addresses the scenario where a register value is saved in
>> some mangled form on the target and gdb does not have sufficient
>> information to demangle it.
>>
>> After getting a register value from an unwinder, gdb will call out to
>> extension languages to allow the value to be demangled.  This avoids
>> having to write a new unwinder and allows the standard unwinders to be
>> used.
>>
>> This patch adds the python class
>> `gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
>> for transformers which provide methods for transforming unwind register
>> values.  Currently one such method is supported, `demangle`.  There is
>> no similar support for mangling values being written back to the target
>> as this was not something I needed to be able to do.  Such support can
>> be added to the python API in future by supporting a `mangle` method.
>> Currently writing back to a demangled register will produce an "Attempt
>> to assign to an unmodifiable value" error.
> I suspect that most cases a single transformer would only have a single
> task, so having an growing list of different transformer method names
> seems unnecessary.
Agreed.

> Why not make UnwindRegisterValueTransformer's callable (add a __call__
> method), and have the signature be:
>
>    def __call__(self, frame_id, reg_descriptor, value, read_p):
>     # ...
>
> Most of these arguments are as for the current 'demangle', but READ_P
> would be true if the value is being read, of FALSE if it's being
> written.
>
> An possibly better alternative would be for the transformers to have two
> methods 'read' and 'write' like:
>
>    def read(self, frame_id, reg_desc, value):
>      # ...
>
>    def write(self, frame_id, reg_desc, value):
>      # ...
I prefer this alternative because if we later support the write case it
should be obvious if an existing transformer is missing an
implementation for it.

> With the idea being that when a register is being read the read method
> is invoked, and when written the write method is invoked, though right
> now you don't need the write method, so I'm not sure how you'd document
> this.  Maybe you'd document it as TODO in the code, but not actually
> document it in the manual?
I'd suggest a TODO in the code and then noting in the documentation that
transforming a value being written back is not currently supported
(without mentioning the `write` method).

I'll put together a v5.

Thanks,
Craig

> This would fully move away from the "demangle" concept and focus more on
> the transformer concept.
>
> Thoughts?
>
> Thanks,
> Andrew
>
>
>
>> Only one transformer can be registered globally at any one time.
>> Attempting to register more than one transformer or to any other locus
>> produces an error.  The function for registering transformers takes a
>> locus to allow other loci to be supported in future.  Registered
>> transformers are stored in a list so that registering multiple
>> transformers may be supported in future.
>>
>> `gdbpy_get_register_descriptor` is now externally visible as it is used
>> to create a gdb.RegisterDescriptor object to pass to the `demangle`
>> method.
>>
>> This patch adds read-only attribute `gdb.Value.lval_type` which
>> transformer methods can use to decide to skip modifying values that did
>> not come directly from the target and may not need transforming, for
>> example, values taken directly from DWARF.
>> ---
>>   gdb/NEWS                                      |  20 +++
>>   gdb/doc/python.texi                           |  93 ++++++++++
>>   gdb/extension-priv.h                          |   8 +
>>   gdb/extension.c                               |  35 ++++
>>   gdb/extension.h                               |   6 +
>>   gdb/frame.c                                   |   9 +
>>   gdb/guile/guile.c                             |   2 +
>>   gdb/python/lib/gdb/__init__.py                |  21 +++
>>   gdb/python/lib/gdb/unwinder.py                |  89 ++++++++-
>>   gdb/python/py-registers.c                     |   2 +-
>>   gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>>   gdb/python/py-value.c                         |  21 +++
>>   gdb/python/python-internal.h                  |   3 +
>>   gdb/python/python.c                           |   4 +-
>>   .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>>   .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>>   .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>>   .../gdb.python/py-unwind-transformer.py       |  48 +++++
>>   gdb/testsuite/gdb.python/py-value.exp         |   8 +
>>   19 files changed, 763 insertions(+), 3 deletions(-)
>>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index e233906153a..085a32c9709 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -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 lvalue type of a value.
>> +
>> +  ** New read-only attribute gdb.Value.lval_type which indicates the
>> +     lvalue 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.
>> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
>> index 96b46a98091..f4db846a36c 100644
>> --- a/gdb/doc/python.texi
>> +++ b/gdb/doc/python.texi
>> @@ -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 lvalue 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 lvalue 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,67 @@ 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.
>> +
>> +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
>> diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
>> index 4ef87415413..deca276d4d6 100644
>> --- a/gdb/extension-priv.h
>> +++ b/gdb/extension-priv.h
>> @@ -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.  */
>> diff --git a/gdb/extension.c b/gdb/extension.c
>> index d8ef8123ab5..82314af1d88 100644
>> --- a/gdb/extension.c
>> +++ b/gdb/extension.c
>> @@ -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");
>> diff --git a/gdb/extension.h b/gdb/extension.h
>> index e351b5b672e..63cbea22c3f 100644
>> --- a/gdb/extension.h
>> +++ b/gdb/extension.h
>> @@ -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) ();
>> diff --git a/gdb/frame.c b/gdb/frame.c
>> index 61d37316c6a..f20b09376e2 100644
>> --- a/gdb/frame.c
>> +++ b/gdb/frame.c
>> @@ -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;
>> diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
>> index ec5f6d08951..4397faeb435 100644
>> --- a/gdb/guile/guile.c
>> +++ b/gdb/guile/guile.c
>> @@ -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
>>   
>> diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
>> index 1a4aefcd802..83aba4552fb 100644
>> --- a/gdb/python/lib/gdb/__init__.py
>> +++ b/gdb/python/lib/gdb/__init__.py
>> @@ -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__))
>> diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
>> index dc456d1987f..37a3db21945 100644
>> --- a/gdb/python/lib/gdb/unwinder.py
>> +++ b/gdb/python/lib/gdb/unwinder.py
>> @@ -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()
>> diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
>> index c6e0748e935..15a72a162c2 100644
>> --- a/gdb/python/py-registers.c
>> +++ b/gdb/python/py-registers.c
>> @@ -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)
>>   {
>> diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
>> index dcf86f7db3d..198a2e4aa74 100644
>> --- a/gdb/python/py-unwind.c
>> +++ b/gdb/python/py-unwind.c
>> @@ -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;
>> +}
>> diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
>> index a5b2303728c..79b72716d0e 100644
>> --- a/gdb/python/py-value.c
>> +++ b/gdb/python/py-value.c
>> @@ -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 */
>> diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
>> index 37bc37691fe..b8d0e2429b8 100644
>> --- a/gdb/python/python-internal.h
>> +++ b/gdb/python/python-internal.h
>> @@ -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);
>> diff --git a/gdb/python/python.c b/gdb/python/python.c
>> index 502fe682b2c..5837c5afcb8 100644
>> --- a/gdb/python/python.c
>> +++ b/gdb/python/python.c
>> @@ -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 */
>> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer-error.py b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>> new file mode 100644
>> index 00000000000..c926f374f1f
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>> @@ -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")
>> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.c b/gdb/testsuite/gdb.python/py-unwind-transformer.c
>> new file mode 100644
>> index 00000000000..9ac9672d325
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer.c
>> @@ -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;
>> +}
>> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.exp b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
>> new file mode 100644
>> index 00000000000..91922e4a286
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
>> @@ -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"
>> diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.py b/gdb/testsuite/gdb.python/py-unwind-transformer.py
>> new file mode 100644
>> index 00000000000..a113aaea9b2
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-unwind-transformer.py
>> @@ -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")
>> diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
>> index a384f4139dd..8b534c786ee 100644
>> --- a/gdb/testsuite/gdb.python/py-value.exp
>> +++ b/gdb/testsuite/gdb.python/py-value.exp
>> @@ -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"
>>   
>> -- 
>> 2.43.0

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v5 0/1] gdb: Add python support for demangling register unwind values
  2026-05-20 13:16       ` [PATCH v4 0/1] " Craig Blackmore
  2026-05-20 13:16         ` [PATCH v4 1/1] " Craig Blackmore
@ 2026-06-08 12:04         ` Craig Blackmore
  2026-06-08 12:04           ` [PATCH v5 1/1] gdb: Add python support for transforming " Craig Blackmore
                             ` (2 more replies)
  1 sibling, 3 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-06-08 12:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, eliz, Craig Blackmore

This version addresses review comments from Andrew Burgess and Eli
Zaretskii from:

  https://sourceware.org/pipermail/gdb-patches/2026-May/227549.html
  https://sourceware.org/pipermail/gdb-patches/2026-May/227536.html

Changes since v4:

* Change `demangle` method to `read`.

* Refer to transforming instead of demangling in the code and
  documentation.

* Fix long line in python.texi.

* Rebase.

Changes since v3:

* Change lval_type to lvalue type in NEWS and python documentation.

* Remove line about future support for multiple transformers or
  different loci from python documentation.

Changes since v2:

* Removed RFC from subject.

* Changed `gdb.unwinder.UnwindRegisterValueDemangler` to
  `gdb.unwinder.UnwindRegisterValueTransformer` so that other transform
  methods could be supported in future rather than being restricted to
  demangling only.  Demangling is now done by calling the `demangle`
  method instead of `__call__`.

* Made transformer registration similar to unwinder registration to
  allow future support for registering multiple transformers and
  registering to other loci.

* Call `invalidate_cached_frames` after registering a transformer.

* Made `gdb.unwinder.UnwindRegisterValueTransformer.name` read-only
  and added type check.

* Pass `gdb.Frame` to demanglers instead of `frame_type`.

* Added global `python_transformer_enabled` to `py-unwind.c` which
  python sets when any transformer is registered.  This allows us to
  skip entering the python environment when demangling and no
  transformers are registered.

* Replaced `gdb.Value.is_lval_register` and `gdb.Value.is_lval_memory`
  read-only boolean attributes with `gdb.Value.lval_type` read-only
  type attribute.

* Removed redundant `return` after call to `error`.

* Replaced `NULL` with `nullptr`.

* Added tests for invalid registration and invalid return value from
  demangler.

* Updated tests and documentation to match the above changes.

* Rebased.

Craig Blackmore (1):
  gdb: Add python support for transforming register unwind values

 gdb/NEWS                                      |  17 ++
 gdb/doc/python.texi                           |  96 ++++++++++
 gdb/extension-priv.h                          |   8 +
 gdb/extension.c                               |  35 ++++
 gdb/extension.h                               |   6 +
 gdb/frame.c                                   |  10 ++
 gdb/guile/guile.c                             |   2 +
 gdb/python/lib/gdb/__init__.py                |  21 +++
 gdb/python/lib/gdb/unwinder.py                |  93 +++++++++-
 gdb/python/py-registers.c                     |   2 +-
 gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
 gdb/python/py-value.c                         |  21 +++
 gdb/python/python-internal.h                  |   3 +
 gdb/python/python.c                           |   4 +-
 .../gdb.python/py-unwind-transformer-error.py |  33 ++++
 .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
 .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
 .../gdb.python/py-unwind-transformer.py       |  48 +++++
 gdb/testsuite/gdb.python/py-value.exp         |   8 +
 19 files changed, 768 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v5 1/1] gdb: Add python support for transforming register unwind values
  2026-06-08 12:04         ` [PATCH v5 0/1] " Craig Blackmore
@ 2026-06-08 12:04           ` Craig Blackmore
  2026-06-08 12:13             ` Eli Zaretskii
  2026-07-15  9:56           ` [PING][PATCH v5 0/1] gdb: Add python support for demangling " Craig Blackmore
  2026-07-29 15:01           ` [PATCH " Craig Blackmore
  2 siblings, 1 reply; 18+ messages in thread
From: Craig Blackmore @ 2026-06-08 12:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, eliz, Craig Blackmore

This patch addresses the scenario where a register value is saved in
some mangled form on the target and gdb does not have sufficient
information to demangle it.

After getting a register value from an unwinder, gdb will call out to
extension languages to allow the value to be transformed.  This avoids
having to write a new unwinder and allows the standard unwinders to be
used.

This patch adds the python class
`gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
for transformers which provide a `read` method for transforming unwind
register values that have been read.  There is no similar support for
transforming values being written back to the target as this was not
something I needed to be able to do.  Such support can be added to the
python API in future by supporting a `write` method.  Currently writing
back to a transformed register will produce an "Attempt to assign to an
unmodifiable value" error.

Only one transformer can be registered globally at any one time.
Attempting to register more than one transformer or to any other locus
produces an error.  The function for registering transformers takes a
locus to allow other loci to be supported in future.  Registered
transformers are stored in a list so that registering multiple
transformers may be supported in future.

`gdbpy_get_register_descriptor` is now externally visible as it is used
to create a gdb.RegisterDescriptor object to pass to the `read` method.

This patch adds read-only attribute `gdb.Value.lval_type` which
transformer methods can use to decide to skip modifying values that did
not come directly from the target and may not need transforming, for
example, values taken directly from DWARF.
---
 gdb/NEWS                                      |  17 ++
 gdb/doc/python.texi                           |  96 ++++++++++
 gdb/extension-priv.h                          |   8 +
 gdb/extension.c                               |  35 ++++
 gdb/extension.h                               |   6 +
 gdb/frame.c                                   |  10 ++
 gdb/guile/guile.c                             |   2 +
 gdb/python/lib/gdb/__init__.py                |  21 +++
 gdb/python/lib/gdb/unwinder.py                |  93 +++++++++-
 gdb/python/py-registers.c                     |   2 +-
 gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
 gdb/python/py-value.c                         |  21 +++
 gdb/python/python-internal.h                  |   3 +
 gdb/python/python.c                           |   4 +-
 .../gdb.python/py-unwind-transformer-error.py |  33 ++++
 .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
 .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
 .../gdb.python/py-unwind-transformer.py       |  48 +++++
 gdb/testsuite/gdb.python/py-value.exp         |   8 +
 19 files changed, 768 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
 create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

diff --git a/gdb/NEWS b/gdb/NEWS
index 7c8cf9af4c2..2678a8b4515 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -482,6 +482,23 @@ 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 lvalue type of a value.
+
+  ** New read-only attribute gdb.Value.lval_type which indicates the
+     lvalue 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 are used to transform unwind
+     register values before they are 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.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 6cafab78d58..02fcbc7cf2b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -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 lvalue 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 lvalue 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,70 @@ 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 a @code{read} method for
+transforming an unwind register value that has been read.
+
+After getting a register value from an unwinder, @value{GDBN} will call
+the @code{read} method of registered transformers to allow them to
+transform the value.  This is useful in case a register value needs
+transforming before @value{GDBN} uses it and avoids the need to write a
+new unwinder.
+
+Transforming unwind register values being written back to the target is
+not currently supported.
+
+@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.
+
+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 read(self, frame, reg, value):
+        """
+        Return new value if transformed, otherwise None.
+        """
+        if not <frame we need to handle>:
+            return None
+
+        if <reg needs transforming>:
+            new_value = ... compute transformed 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
diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index 4ef87415413..6af96b16c50 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -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 (*transform_unwind_register_value) (frame_info_ptr frame,
+						       int regnum,
+						       struct value **value);
 };
 
 /* State necessary to restore a signal handler to its previous value.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index d8ef8123ab5..7adaabd450b 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -1139,6 +1139,41 @@ ext_lang_before_prompt (const char *current_gdb_prompt)
     }
 }
 
+/* See extension.h.  */
+
+struct value *
+ext_lang_transform_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->transform_unwind_register_value == nullptr)
+	continue;
+
+      rc = extlang->ops->transform_unwind_register_value (frame, regnum, &val);
+      /* If the transformer 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 transformer did not change the value then continue calling
+	   other transformers for other extension languages.  */
+	case EXT_LANG_RC_NOP:
+	  break;
+	default:
+	  gdb_assert_not_reached (
+	    "bad return from transform_unwind_register_value");
+	}
+    }
+  return val;
+}
+
 INIT_GDB_FILE (extension)
 {
   gdb::observers::before_prompt.attach (ext_lang_before_prompt, "extension");
diff --git a/gdb/extension.h b/gdb/extension.h
index e351b5b672e..abc8b4affa6 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -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 transformed value.  */
+
+extern struct value * ext_lang_transform_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) ();
diff --git a/gdb/frame.c b/gdb/frame.c
index 179e251eb1f..3259531736a 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1338,6 +1338,16 @@ frame_unwind_register_value (const frame_info_ptr &next_frame, int regnum)
 	       user_reg_map_regnum_to_name (gdbarch, regnum));
     }
 
+  /* Allow extension languages to transform the unwound value.
+
+     Do not transform before frame id has been computed as this would cause
+     recursion as gdbpy_transform_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_transform_unwind_register_value (next_frame, regnum,
+						      value);
+
   if (frame_debug)
     {
       string_file debug_file;
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index ec5f6d08951..1bd92266c78 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -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_transform_unwind_register_value */
 };
 #endif
 
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 1a4aefcd802..5839168d01c 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -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_transformers(frame, reg, value):
+    """Internal function called from GDB to execute unwind register value
+       transformers.
+
+    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].read(frame, reg, value)
+
+    return None
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
index dc456d1987f..1e66f807f93 100644
--- a/gdb/python/lib/gdb/unwinder.py
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -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,94 @@ 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 read(self, frame, reg, value):
+        """GDB calls this method to transform a register unwind value that has
+        been read.
+
+        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 read.")
+
+    # TODO GDB only supports transforming read values.  If adding support for
+    # transforming values being written, add a `write` method here.
+
+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()
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index cc7b09079da..797ddc3de83 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -145,7 +145,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)
 {
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index 13eced4cecb..bc5d1498ea3 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -30,6 +30,7 @@
 #include "stack.h"
 #include "charset.h"
 #include "block.h"
+#include "value.h"
 
 
 /* Debugging of Python unwinders.  */
@@ -1019,6 +1020,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;
+  return py_none ().release ();
+}
+
+/* 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
@@ -1032,6 +1089,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;
 }
 
@@ -1185,3 +1255,102 @@ PyTypeObject unwind_info_object_type =
   0,                              /* tp_init */
   0,                              /* tp_alloc */
 };
+
+/* Execute python transformers and handle the result.  */
+
+enum ext_lang_rc
+gdbpy_transform_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_transform_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 transformer.  */
+  const char *func_name = "_execute_unwind_register_value_transformers";
+  if (gdb_python_module == nullptr
+      || ! PyObject_HasAttrString (gdb_python_module, func_name))
+    {
+      PyErr_SetString (PyExc_NameError,
+	"Installation error: gdb._execute_unwind_register_value_transformers "
+	"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;
+    }
+
+  /* Transformer 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 `read` 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 transformed value.  */
+  *val = new_val;
+
+  return EXT_LANG_RC_OK;
+}
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index 5b38110396e..608f4ed1fa5 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -1407,6 +1407,19 @@ valpy_fetch_lazy (PyObject *self, PyObject *args)
   return py_none ().release ();
 }
 
+/* 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
@@ -2246,6 +2259,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);
 }
 
@@ -2270,6 +2289,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 */
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 0cc5fc4ac06..d5594c17d0e 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -452,6 +452,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_transform_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,
@@ -526,6 +528,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);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 2131c4a0648..15cc8d1c4b3 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -186,7 +186,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_transform_unwind_register_value
 };
 
 #endif /* HAVE_PYTHON */
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer-error.py b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
new file mode 100644
index 00000000000..67a8ba3e112
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer-error.py
@@ -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 read(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")
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.c b/gdb/testsuite/gdb.python/py-unwind-transformer.c
new file mode 100644
index 00000000000..9ac9672d325
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.exp b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
new file mode 100644
index 00000000000..2c354fb391f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.exp
@@ -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 does not need transforming.
+# * Frame #2 is not ok because its frame address and program counter are mangled
+# in memory and need transforming.
+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 transformed 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 transformed 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 transformed 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 `read` 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"
diff --git a/gdb/testsuite/gdb.python/py-unwind-transformer.py b/gdb/testsuite/gdb.python/py-unwind-transformer.py
new file mode 100644
index 00000000000..b43027c3e46
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-transformer.py
@@ -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 read1(self, value):
+        """
+        For this test case transformed values are the bitwise negation of the
+        original value.
+        """
+        mask = 0xffffffffffffffff
+        return value.cast(gdb.Value (mask).type) ^ mask
+
+    def read(self, frame, reg, value):
+        """
+        Transform 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.read1(value)
+
+        return None
+
+
+test_transformer = TestUnwindRegisterValueTransformer()
+gdb.unwinder.register_unwind_register_value_transformer(None, test_transformer, False)
+
+print("Python script imported")
diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
index ba3b1376ff5..126f714c0c1 100644
--- a/gdb/testsuite/gdb.python/py-value.exp
+++ b/gdb/testsuite/gdb.python/py-value.exp
@@ -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"
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v5 1/1] gdb: Add python support for transforming register unwind values
  2026-06-08 12:04           ` [PATCH v5 1/1] gdb: Add python support for transforming " Craig Blackmore
@ 2026-06-08 12:13             ` Eli Zaretskii
  0 siblings, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2026-06-08 12:13 UTC (permalink / raw)
  To: Craig Blackmore; +Cc: gdb-patches, aburgess

> From: Craig Blackmore <craig.blackmore@embecosm.com>
> Cc: aburgess@redhat.com,
> 	eliz@gnu.org,
> 	Craig Blackmore <craig.blackmore@embecosm.com>
> Date: Mon,  8 Jun 2026 13:04:37 +0100
> 
> This patch addresses the scenario where a register value is saved in
> some mangled form on the target and gdb does not have sufficient
> information to demangle it.
> 
> After getting a register value from an unwinder, gdb will call out to
> extension languages to allow the value to be transformed.  This avoids
> having to write a new unwinder and allows the standard unwinders to be
> used.
> 
> This patch adds the python class
> `gdb.unwinder.UnwindRegisterValueTransformer` which is the base class
> for transformers which provide a `read` method for transforming unwind
> register values that have been read.  There is no similar support for
> transforming values being written back to the target as this was not
> something I needed to be able to do.  Such support can be added to the
> python API in future by supporting a `write` method.  Currently writing
> back to a transformed register will produce an "Attempt to assign to an
> unmodifiable value" error.
> 
> Only one transformer can be registered globally at any one time.
> Attempting to register more than one transformer or to any other locus
> produces an error.  The function for registering transformers takes a
> locus to allow other loci to be supported in future.  Registered
> transformers are stored in a list so that registering multiple
> transformers may be supported in future.
> 
> `gdbpy_get_register_descriptor` is now externally visible as it is used
> to create a gdb.RegisterDescriptor object to pass to the `read` method.
> 
> This patch adds read-only attribute `gdb.Value.lval_type` which
> transformer methods can use to decide to skip modifying values that did
> not come directly from the target and may not need transforming, for
> example, values taken directly from DWARF.
> ---
>  gdb/NEWS                                      |  17 ++
>  gdb/doc/python.texi                           |  96 ++++++++++
>  gdb/extension-priv.h                          |   8 +
>  gdb/extension.c                               |  35 ++++
>  gdb/extension.h                               |   6 +
>  gdb/frame.c                                   |  10 ++
>  gdb/guile/guile.c                             |   2 +
>  gdb/python/lib/gdb/__init__.py                |  21 +++
>  gdb/python/lib/gdb/unwinder.py                |  93 +++++++++-
>  gdb/python/py-registers.c                     |   2 +-
>  gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>  gdb/python/py-value.c                         |  21 +++
>  gdb/python/python-internal.h                  |   3 +
>  gdb/python/python.c                           |   4 +-
>  .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>  .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>  .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>  .../gdb.python/py-unwind-transformer.py       |  48 +++++
>  gdb/testsuite/gdb.python/py-value.exp         |   8 +
>  19 files changed, 768 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py

Thanks, the documentation parts are okay.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PING][PATCH v5 0/1] gdb: Add python support for demangling register unwind values
  2026-06-08 12:04         ` [PATCH v5 0/1] " Craig Blackmore
  2026-06-08 12:04           ` [PATCH v5 1/1] gdb: Add python support for transforming " Craig Blackmore
@ 2026-07-15  9:56           ` Craig Blackmore
  2026-07-29 15:01           ` [PATCH " Craig Blackmore
  2 siblings, 0 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-07-15  9:56 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess

Ping.

Thanks,

Craig

On 08/06/2026 13:04, Craig Blackmore wrote:
> This version addresses review comments from Andrew Burgess and Eli
> Zaretskii from:
>
>    https://sourceware.org/pipermail/gdb-patches/2026-May/227549.html
>    https://sourceware.org/pipermail/gdb-patches/2026-May/227536.html
>
> Changes since v4:
>
> * Change `demangle` method to `read`.
>
> * Refer to transforming instead of demangling in the code and
>    documentation.
>
> * Fix long line in python.texi.
>
> * Rebase.
>
> Changes since v3:
>
> * Change lval_type to lvalue type in NEWS and python documentation.
>
> * Remove line about future support for multiple transformers or
>    different loci from python documentation.
>
> Changes since v2:
>
> * Removed RFC from subject.
>
> * Changed `gdb.unwinder.UnwindRegisterValueDemangler` to
>    `gdb.unwinder.UnwindRegisterValueTransformer` so that other transform
>    methods could be supported in future rather than being restricted to
>    demangling only.  Demangling is now done by calling the `demangle`
>    method instead of `__call__`.
>
> * Made transformer registration similar to unwinder registration to
>    allow future support for registering multiple transformers and
>    registering to other loci.
>
> * Call `invalidate_cached_frames` after registering a transformer.
>
> * Made `gdb.unwinder.UnwindRegisterValueTransformer.name` read-only
>    and added type check.
>
> * Pass `gdb.Frame` to demanglers instead of `frame_type`.
>
> * Added global `python_transformer_enabled` to `py-unwind.c` which
>    python sets when any transformer is registered.  This allows us to
>    skip entering the python environment when demangling and no
>    transformers are registered.
>
> * Replaced `gdb.Value.is_lval_register` and `gdb.Value.is_lval_memory`
>    read-only boolean attributes with `gdb.Value.lval_type` read-only
>    type attribute.
>
> * Removed redundant `return` after call to `error`.
>
> * Replaced `NULL` with `nullptr`.
>
> * Added tests for invalid registration and invalid return value from
>    demangler.
>
> * Updated tests and documentation to match the above changes.
>
> * Rebased.
>
> Craig Blackmore (1):
>    gdb: Add python support for transforming register unwind values
>
>   gdb/NEWS                                      |  17 ++
>   gdb/doc/python.texi                           |  96 ++++++++++
>   gdb/extension-priv.h                          |   8 +
>   gdb/extension.c                               |  35 ++++
>   gdb/extension.h                               |   6 +
>   gdb/frame.c                                   |  10 ++
>   gdb/guile/guile.c                             |   2 +
>   gdb/python/lib/gdb/__init__.py                |  21 +++
>   gdb/python/lib/gdb/unwinder.py                |  93 +++++++++-
>   gdb/python/py-registers.c                     |   2 +-
>   gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>   gdb/python/py-value.c                         |  21 +++
>   gdb/python/python-internal.h                  |   3 +
>   gdb/python/python.c                           |   4 +-
>   .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>   .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>   .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>   .../gdb.python/py-unwind-transformer.py       |  48 +++++
>   gdb/testsuite/gdb.python/py-value.exp         |   8 +
>   19 files changed, 768 insertions(+), 3 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py
>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v5 0/1] gdb: Add python support for demangling register unwind values
  2026-06-08 12:04         ` [PATCH v5 0/1] " Craig Blackmore
  2026-06-08 12:04           ` [PATCH v5 1/1] gdb: Add python support for transforming " Craig Blackmore
  2026-07-15  9:56           ` [PING][PATCH v5 0/1] gdb: Add python support for demangling " Craig Blackmore
@ 2026-07-29 15:01           ` Craig Blackmore
  2 siblings, 0 replies; 18+ messages in thread
From: Craig Blackmore @ 2026-07-29 15:01 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess

I have just been looking at the proposed release schedule and I 
wondered, if no further changes are needed, whether it would be possible 
to include this patch before branching for GDB 18?

Thanks,
Craig

On 08/06/2026 13:04, Craig Blackmore wrote:
> This version addresses review comments from Andrew Burgess and Eli
> Zaretskii from:
>
>    https://sourceware.org/pipermail/gdb-patches/2026-May/227549.html
>    https://sourceware.org/pipermail/gdb-patches/2026-May/227536.html
>
> Changes since v4:
>
> * Change `demangle` method to `read`.
>
> * Refer to transforming instead of demangling in the code and
>    documentation.
>
> * Fix long line in python.texi.
>
> * Rebase.
>
> Changes since v3:
>
> * Change lval_type to lvalue type in NEWS and python documentation.
>
> * Remove line about future support for multiple transformers or
>    different loci from python documentation.
>
> Changes since v2:
>
> * Removed RFC from subject.
>
> * Changed `gdb.unwinder.UnwindRegisterValueDemangler` to
>    `gdb.unwinder.UnwindRegisterValueTransformer` so that other transform
>    methods could be supported in future rather than being restricted to
>    demangling only.  Demangling is now done by calling the `demangle`
>    method instead of `__call__`.
>
> * Made transformer registration similar to unwinder registration to
>    allow future support for registering multiple transformers and
>    registering to other loci.
>
> * Call `invalidate_cached_frames` after registering a transformer.
>
> * Made `gdb.unwinder.UnwindRegisterValueTransformer.name` read-only
>    and added type check.
>
> * Pass `gdb.Frame` to demanglers instead of `frame_type`.
>
> * Added global `python_transformer_enabled` to `py-unwind.c` which
>    python sets when any transformer is registered.  This allows us to
>    skip entering the python environment when demangling and no
>    transformers are registered.
>
> * Replaced `gdb.Value.is_lval_register` and `gdb.Value.is_lval_memory`
>    read-only boolean attributes with `gdb.Value.lval_type` read-only
>    type attribute.
>
> * Removed redundant `return` after call to `error`.
>
> * Replaced `NULL` with `nullptr`.
>
> * Added tests for invalid registration and invalid return value from
>    demangler.
>
> * Updated tests and documentation to match the above changes.
>
> * Rebased.
>
> Craig Blackmore (1):
>    gdb: Add python support for transforming register unwind values
>
>   gdb/NEWS                                      |  17 ++
>   gdb/doc/python.texi                           |  96 ++++++++++
>   gdb/extension-priv.h                          |   8 +
>   gdb/extension.c                               |  35 ++++
>   gdb/extension.h                               |   6 +
>   gdb/frame.c                                   |  10 ++
>   gdb/guile/guile.c                             |   2 +
>   gdb/python/lib/gdb/__init__.py                |  21 +++
>   gdb/python/lib/gdb/unwinder.py                |  93 +++++++++-
>   gdb/python/py-registers.c                     |   2 +-
>   gdb/python/py-unwind.c                        | 169 ++++++++++++++++++
>   gdb/python/py-value.c                         |  21 +++
>   gdb/python/python-internal.h                  |   3 +
>   gdb/python/python.c                           |   4 +-
>   .../gdb.python/py-unwind-transformer-error.py |  33 ++++
>   .../gdb.python/py-unwind-transformer.c        |  80 +++++++++
>   .../gdb.python/py-unwind-transformer.exp      | 115 ++++++++++++
>   .../gdb.python/py-unwind-transformer.py       |  48 +++++
>   gdb/testsuite/gdb.python/py-value.exp         |   8 +
>   19 files changed, 768 insertions(+), 3 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer-error.py
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.c
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.exp
>   create mode 100644 gdb/testsuite/gdb.python/py-unwind-transformer.py
>

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-07-29 15:02 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-10 11:04 [RFC PATCH] gdb: Add python support for demangling register unwind values Craig Blackmore
2025-04-10 12:31 ` Eli Zaretskii
2025-05-08 10:17 ` [RFC PATCH v2] " Craig Blackmore
2025-05-08 11:40   ` Eli Zaretskii
2025-05-12 13:15   ` Andrew Burgess
2026-04-30 15:19     ` Craig Blackmore
2026-04-30 15:31     ` [PATCH v3] " Craig Blackmore
2026-04-30 16:05       ` Eli Zaretskii
2026-05-20 13:16       ` [PATCH v4 0/1] " Craig Blackmore
2026-05-20 13:16         ` [PATCH v4 1/1] " Craig Blackmore
2026-05-20 15:44           ` Eli Zaretskii
2026-05-21  9:47           ` Andrew Burgess
2026-05-27 10:49             ` Craig Blackmore
2026-06-08 12:04         ` [PATCH v5 0/1] " Craig Blackmore
2026-06-08 12:04           ` [PATCH v5 1/1] gdb: Add python support for transforming " Craig Blackmore
2026-06-08 12:13             ` Eli Zaretskii
2026-07-15  9:56           ` [PING][PATCH v5 0/1] gdb: Add python support for demangling " Craig Blackmore
2026-07-29 15:01           ` [PATCH " Craig Blackmore

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox