From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id IGJVIYXMFmrg9h8AWB0awg (envelope-from ) for ; Wed, 27 May 2026 06:50:45 -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=OXSrETDC; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 62C3F1E062; Wed, 27 May 2026 06:50:45 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.4 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_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (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 88B241E062 for ; Wed, 27 May 2026 06:50:39 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 206084BA2E25 for ; Wed, 27 May 2026 10:50:38 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 206084BA2E25 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=OXSrETDC Received: from mail-wm1-x32e.google.com (mail-wm1-x32e.google.com [IPv6:2a00:1450:4864:20::32e]) by sourceware.org (Postfix) with ESMTPS id 7003F4BA2E27 for ; Wed, 27 May 2026 10:49:57 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7003F4BA2E27 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 7003F4BA2E27 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::32e ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779878997; cv=none; b=MhKePeWCq9fWO8EdCAMHuvdSGKEJUBKXe1SBKy4/wJPA6w/gcP7174bskmzKT4PoFnbbH5Fh17Jkj3sYtj9rT/CMBhRJjH6brbGSXmlAp/bKURZ55xWpaoQhqzt3b4I6qxkEZ0txamL6tOwVR/az5xrpN9B7Ts6fQeeHzahmwq8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779878997; c=relaxed/simple; bh=wWUtKd17XgrVv9gV7BBWWna9ZW9KfJXbnKqIkxdCOm0=; h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From; b=KimsHxwGLQ+mUIJTJaBJURNR7FiN9bx9jTtOcDlX4wNO5Fq0bI+1ysTq5NGUTck/p4iSLHNWevHnf6q5zaPnlTPwcdbTz+UQLId7XCDfYq+/BthGMW0HoQopncDEvwWC6fDQDKJ0bkrQlPoDm81A+20SzaA/kpdheFpvcplCc8g= ARC-Authentication-Results: i=1; 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=OXSrETDC DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7003F4BA2E27 Received: by mail-wm1-x32e.google.com with SMTP id 5b1f17b1804b1-49042aeeb75so67025725e9.1 for ; Wed, 27 May 2026 03:49:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=embecosm.com; s=google; t=1779878996; x=1780483796; darn=sourceware.org; h=content-transfer-encoding:in-reply-to:autocrypt:from :content-language:references:cc:to:subject:user-agent:mime-version :date:message-id:from:to:cc:subject:date:message-id:reply-to; bh=+YDGAMU86l+MVCHIyKZ1+EHFujNZa/8ShdjM9zQq0IE=; b=OXSrETDCUTB5fclWFLwsIUwcp0iPiP73tct87v6tbNl88sZ7eQY4dbOL5vw8pLGtd7 mYsM6c07j7iaKYl9qYyMXSR45hvhBXu7FVsncPlPoSMErdF4iD9c8qAXxqr0c8KbpEPH Tf4e2ZvWCtJZkzcmrPrnSjMU/nLQPZGYM/neUud475JXgWLtLRdGWqn8lh9cw9h2w55R +k0SZgeKxHkekoFaOdV1yW8VWUz4/HmQww96iRAjrWlVnsadx5LxS8YvtMuRDA6861g3 CM53Mb6kKMNs0s9HZhJviDAbvhrnlicWmbLM9UdP9Q22GHGEDW8M9OxGKFoG2xfdywoC Wsmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779878996; x=1780483796; h=content-transfer-encoding:in-reply-to:autocrypt:from :content-language:references:cc:to:subject:user-agent:mime-version :date:message-id:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=+YDGAMU86l+MVCHIyKZ1+EHFujNZa/8ShdjM9zQq0IE=; b=KRjmGF+G/fqb+BCOVchiQ84qlx3W9sBJIP+NPX3Tb9tzmMcreqcX2MEbBijOBVFHTY D1UJXq4yACvWC5ZguA9d3xKqbPadRv1iwI4xTeVmTkHWs3gs4MgJeUt2g3/WsCyfluJj 1+h8pW8jw7of9KFlFRiLkTBwldPQj/V5GT/aibwDhhmDBZbiiIacliCDMMa5M9Aug6XQ EPL7E1+KbVlxK2fX5SF00xdxFHxQjj+Eq9FhU0l0bzOEla6AHEao3KE5cTx4u3WdNTeM U1Xbk89ltEJIeAXEs8YsXPoRbMeZWKCYs0IanTzBMyRwdYZES2ZmhgGWRYkQpsjUrFxv ktvQ== X-Forwarded-Encrypted: i=1; AFNElJ9DLew9g+snBSZLf0qZEcLkOQSGuOzBYFL9na8hj0TfVcRul3VBlS7f86iYbx/jpqypx61z+j6Awbaxnw==@sourceware.org X-Gm-Message-State: AOJu0YzMVGnerjMlW6wDABrzeXhYLd7aYDtsdNbu3dbZXBKwQmVfNs83 7SoNFf33vrlsxxysVgxTufctGCcFJ17HKJq1K5l/RyLBnf3k7EMt6Q8lhEjKeX3R45s= X-Gm-Gg: Acq92OEQHuTjbFxoEitql5l3Ml20ubN6Zj2iOZy3WpZh3Zj7H9c/u09UtybGS9aEL3L SiTaAHCAJCdHa56rPda4HHlYvXa8m2gcomAubcwncwBIKEgqHMYJjI3n9WCscyDS1u9NcW/vE3j CiQkSnHotMqNZfMIEMD+qhsy1GZ25oyMCUd76/E/7pYw2hK0B2TVtdElbCbv2LDeaGhLWrXVS4s KFe0f3NVJyxeRAAyrT/UZn4dx3xtiC/1/IyyXzlhB8oXwKOBaXOiVZM65Ccu0N2m28phzzSoI9f 4BiYD9bpmBGM82yK6awrx718B1lGf7UgKhSQrmIwLPjcPE1suzAjwAEjw8/gL9apbNYSHy9Euh5 WH3vfQDqtX4uRbBcpNxgpxzaVcnYsnxFhCnuotPts1Fj/Fqne1JYXnfW40sQ5wOlsra0a1tSIgR DBQ65aHbnar+aXW0A88bmGPN2/mhup6Wl8+DBkkQ== X-Received: by 2002:a05:600c:46c6:b0:490:46df:a87a with SMTP id 5b1f17b1804b1-49046dfa9c1mr394688905e9.1.1779878994993; Wed, 27 May 2026 03:49:54 -0700 (PDT) Received: from [192.168.0.192] ([212.69.42.53]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4904561a33dsm355782475e9.11.2026.05.27.03.49.54 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 27 May 2026 03:49:54 -0700 (PDT) Message-ID: <54c4f94a-9982-4af9-8473-7ab58184f7dc@embecosm.com> Date: Wed, 27 May 2026 11:49:53 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v4 1/1] gdb: Add python support for demangling register unwind values To: Andrew Burgess , gdb-patches@sourceware.org Cc: eliz@gnu.org References: <20260430153155.979053-1-craig.blackmore@embecosm.com> <20260520131607.32885-1-craig.blackmore@embecosm.com> <20260520131607.32885-2-craig.blackmore@embecosm.com> <87a4ttytz3.fsf@redhat.com> Content-Language: en-US From: Craig Blackmore Autocrypt: addr=craig.blackmore@embecosm.com; keydata= xsBNBFdIF8oBCACwrsvc6YVfzJRT+ZoBfL9jEb8ITwNahDxCGSG6sIWrJ9UFeTwE8fnNhMpz RyFRm0OXruS5k/8YHJHrxKxFY9cgZ3CWNftXEjRqURUWGtN/ESiw0J7nVfhSGQTo3LBzpXZ1 0JHk4ZHKDJKYa+fhybCHOs19BfP3HydHoTlc5QTKMfom0X/xo7WDdwUYeZsjD9u8IzHk7gNw 05Abk1vqni+J7Fghjp4RI8W3IsjpKOfV3f02OyO/MTSraXNyejO4JRl0A8b3q1Lq+G6Z7o5n LVief5JpkRyzWQSawTIBKmRZa9EzAKZXd6IJdY/sZt7pTir5EP7MHq4a+AtKfKuDkrDDABEB AAHNLkNyYWlnIEJsYWNrbW9yZSA8Y3JhaWcuYmxhY2ttb3JlQGVtYmVjb3NtLmNvbT7CwHgE EwECACIFAldIF8oCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEGEeRQLLl5WtydMH /1nYd9jmOBaF8w5gGgjF5eOO5b/cdUegmO///VYj/5R7iF/zbB6KgF0Obo5h2gG9AIfsZG+T ybuTx7oU1DZYEIndw+YP9c9Yi5de5UzEHwbJiV57W0n+MP0Widgw7p6XJmUQ1XbHxdcWp7nY EJa8ASKLuuIhO8JFUXbQ8BcUiWbsA/JxgCzeid8iixGrzPWj6iFzoK2mX4GqP+24pXSDUamM TXmSQd2taYEsyUdJNiEkUC51ncRcMuThjdtfn6Ok+7lHjh3Zz8q0keJz5pnIp4EXdkAgKSjq U42PMrd3v1HoIFINTtr5F23OdkxoQzysu4GMO4pkw5pwz95Uckr08ojOwE0EV0gXygEIAKv/ luYHmCG/qefgzdbnegwMdG5753NJ+zGxFltFX6aaOPZ8go9Omf6zwjybUKv6Qx6AlDanwCl3 ewVQs+h9iW8uaQBRgeDmwAGMG/doBiFqs7X0jBf23exMiJezXlKb2ZlKzMAbzJ87408AzRaV sZdwEpXHVi2mRPoXtMrqL5iQEyG5hdx2ySj5164DIgVOs/ypFiaiFaDPkIcAQTzJrxsbt6pf iI9kT93DO9nRKVV0pPWztV8P5gKM8HY2rS0wQcfrqAU6T89Aa0VFw92J+w5d2spF8MUNPsvR NLm9ooCF3YME9STYHXrNH1U9fJUWpIC+b49UoWSWRD9nwl2h2i8AEQEAAcLAXwQYAQIACQUC V0gXygIbDAAKCRBhHkUCy5eVreLbB/sHPs1xu78uNV8O4UPTX7D5zBBS3nsrbDr+8stmXRap xbvo6kqKzIMAXuO3bYB/NyJ/tFzuFr9Tjd/2g56D2186bp01/kgxJ9CEl/m2T3lG3DlxIoLg pCExzTLTb8zH/7/6mdeJ17cdnrK+2QAKYctReVPAC67cq5KmUyU3bv5e1JzhV4ezz/i/O+Jv el112ZEsa54ya9KZOUHbgAR6hLnRWIa+8yQTtXqYRc3LxLRfS80Wn0Err1YvqFYzJsQMC8ND xAeEuqQ1gfk1b0jmv7tYljNqsHqzGVbuWz6hyzyLv5GjcdSDKpbw/797gRKQSY8Gty5ynfUH O4kKyuZPrE8P In-Reply-To: <87a4ttytz3.fsf@redhat.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit 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 On 21/05/2026 10:47, Andrew Burgess wrote: > Craig Blackmore 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 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 : >> + return None >> + >> + if : >> + 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 . >> + >> +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 . */ >> + >> +/* 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 . >> + >> +# 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 : 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 . >> + >> +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