From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id SonaLC6FHGiWBBoAWB0awg (envelope-from ) for ; Thu, 08 May 2025 06:19:26 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=embecosm.com header.i=@embecosm.com header.a=rsa-sha256 header.s=google header.b=B+ZoI0qs; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id A57E11E10E; Thu, 8 May 2025 06:19:26 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-9.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED,RCVD_IN_VALIDITY_RPBL, RCVD_IN_VALIDITY_SAFE autolearn=ham autolearn_force=no version=4.0.1 Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 1D1161E092 for ; Thu, 8 May 2025 06:19:25 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id B20653858417 for ; Thu, 8 May 2025 10:19:24 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B20653858417 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=embecosm.com header.i=@embecosm.com header.a=rsa-sha256 header.s=google header.b=B+ZoI0qs Received: from mail-wr1-x435.google.com (mail-wr1-x435.google.com [IPv6:2a00:1450:4864:20::435]) by sourceware.org (Postfix) with ESMTPS id 82E953858C62 for ; Thu, 8 May 2025 10:18:45 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 82E953858C62 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=embecosm.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=embecosm.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 82E953858C62 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::435 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1746699525; cv=none; b=KrJIsqgDhUZK/74pFHxcuZzxmG6LX6fP98A5M0BRlCl6CXWXlYrvjgZ6y/I4dUXz9TKPAOlKO0gC0OVJ69LLPfGgeZ/Cu6FkGGKmhbgmcpKvEomWTVBy/NPE287GBjKya2Fv7oCP7OyOqC2hlzyF+2/gmRNexm4vhjQW7SqoQsE= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1746699525; c=relaxed/simple; bh=+NaUHTQBYG+jlhEbYx+QfFqBNoAXY1CJ5Q4me2WvJyM=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=qvXnKmYk2wcDZOuOjbBR+WmpN49BfX6jEwTFj0Py87xFrxyogXuHznx1QpD0/HlVWn+3xcM3lEiYRLLrQxNCUX2qhmUfYhesSe8zkaPWPg1/jj7sxVrXRTA+aEmyzAXpUAT5zsR4hC7uAI1nrmfO0d+pB5vyyzeF1yFIOhul9lI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 82E953858C62 Received: by mail-wr1-x435.google.com with SMTP id ffacd0b85a97d-3a0be50048eso245487f8f.0 for ; Thu, 08 May 2025 03:18:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=embecosm.com; s=google; t=1746699524; x=1747304324; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=BIbso6SFJJNZtCT1nD0GjNHzcJiVbLwA6jqHNcMHIrU=; b=B+ZoI0qsZy4VE92nJf9e9QdUzROrcJ3ei63zs6UOX9vsujpwQH23303Y5983kl20V4 BssVucFklerSTC9xL3ygofRqHIaPph/QKavDXvdq9jnoMF6k7yk1ltZr2ngENm8MmqLD L62HwDrz36EGNXVMHJ8dsWCV/VxlTLVHVVUsOTva/JwvfYs1eeKEzKj1DsHwR3KuT46w WoqoRZzjthapD0ESFvpaPuqN7xxQquXfBK+mEb0CTjmRuOBolrIFvCDqCW5PI7bmsYuT 7c2t7Qm3kbwuYmtdttGyKr2oobC/2O3TdCg1NV0VWkPasfkf7LFjvIunzrjQCCNmEHUm fvfg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1746699524; x=1747304324; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=BIbso6SFJJNZtCT1nD0GjNHzcJiVbLwA6jqHNcMHIrU=; b=DRAuaOruWSqX47c/wS8giHQ0byU7VWIYwFuAP2aX3TLuKRWxNHFAkKnhYC6vzgsxCK nK2HqENZJvnZkzcv11mxpoNA0wc+BW0tJPZehIY+J1EIPFdIQYydCpX/CW6ilZTdN+/6 oJePCMz2wwralpHBW4lXB7XQXI/ONoWGR7qnPIvXXiTUKXc/3UVj114nPel1zbMI1Yo+ X6GqCgCuRTQNB8lkfquBjnDsNkE4IViOjV/UprcZPij6HbKcDA7J8Pwu8hWXljBDk7Dq ozfx9ksyFCl32v5sOlnze0owb5AjENf6nAQG0A+MpeNwHzFfU/CliwamZ1kheIeaFJbB om5g== X-Gm-Message-State: AOJu0YyYphlhwcKO6rkcbXNIGrvqWqFekN0Cqzls1CQW1PMmgShnK4rG num6LuxtVuynrvxfecuS/gKSmLAttg74ezI0IM+i0TlqqlX0M/VqGICgQGFBMwAPECanG0QzJC2 e X-Gm-Gg: ASbGncugp8AtG4f3yf47EH8TqesBpEYK39g+Wd8GbirBY/qV/e1B6hFhz1YPob0dQxJ fHBN6+FCmhEfpwt6OS549AOOuah/Od+3to2v7qmR0cwcHC3p5LjPmXs0zBxoHtQ0mC7JGuMnm+o GClVFXZ/Zo+3LL9k3iXPyrVPfbALIhpS7p9vdW9vnTfZ3yS7bOGnOUiyvcIpXFuYF6UOwCkjEjA ap9+Yk0P4v3u6D668/l3L9DL3fbhJnPw6t7R5PFbZtTvpWsHdu6LqMlFYSXsPkUdnWpH3cdalvQ qAJrN/RNU0L50TCdjlSkfy8vpE99FR7txzQwICEmtstNpOO8fdZji3ZomSuXtZkeYiODqkOu X-Google-Smtp-Source: AGHT+IGscR0Im9t3vYONl6ucPz6BsjuwqO5mwj13zoQoVQmDFwrM0RWR4TkUdvc1gBz9jAnpqEvqeA== X-Received: by 2002:a05:6000:40e1:b0:3a0:b1f7:c1da with SMTP id ffacd0b85a97d-3a0ba0efb52mr2286967f8f.46.1746699523835; Thu, 08 May 2025 03:18:43 -0700 (PDT) Received: from dorian.sou.embecosm-corp.com ([212.69.42.53]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3a100f9c980sm598105f8f.72.2025.05.08.03.18.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 08 May 2025 03:18:43 -0700 (PDT) From: Craig Blackmore To: gdb-patches@sourceware.org Cc: Craig Blackmore , Eli Zaretskii Subject: [RFC PATCH v2] gdb: Add python support for demangling register unwind values Date: Thu, 8 May 2025 11:17:21 +0100 Message-ID: <20250508101721.2000793-1-craig.blackmore@embecosm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250410110426.3488955-1-craig.blackmore@embecosm.com> References: <20250410110426.3488955-1-craig.blackmore@embecosm.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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 --- 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 != : + return None + + if reg.name == : + 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 . */ + +/* 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 . + +# 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 . + +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