From: Craig Blackmore <craig.blackmore@embecosm.com>
To: Andrew Burgess <aburgess@redhat.com>, gdb-patches@sourceware.org
Cc: eliz@gnu.org
Subject: Re: [PATCH v4 1/1] gdb: Add python support for demangling register unwind values
Date: Wed, 27 May 2026 11:49:53 +0100 [thread overview]
Message-ID: <54c4f94a-9982-4af9-8473-7ab58184f7dc@embecosm.com> (raw)
In-Reply-To: <87a4ttytz3.fsf@redhat.com>
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
next prev parent reply other threads:[~2026-05-27 10:50 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-10 11:04 [RFC PATCH] " 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 [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=54c4f94a-9982-4af9-8473-7ab58184f7dc@embecosm.com \
--to=craig.blackmore@embecosm.com \
--cc=aburgess@redhat.com \
--cc=eliz@gnu.org \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox