@@ -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
@@ -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
@@ -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. */
@@ -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 ()
@@ -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) ();
@@ -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;
@@ -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
@@ -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__))
@@ -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__.")
@@ -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)
{
@@ -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;
+}
@@ -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 */
@@ -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);
@@ -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 */
new file mode 100644
@@ -0,0 +1,80 @@
+/* This test program is part of GDB, the GNU debugger.
+
+ Copyright 2025 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This is the test program loaded into GDB by the py-unwind-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;
+}
new file mode 100644
@@ -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"
new file mode 100644
@@ -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()
@@ -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"