From: Andrew Burgess <aburgess@redhat.com>
To: Kevin Buettner <kevinb@redhat.com>, gdb-patches@sourceware.org
Cc: Kevin Buettner <kevinb@redhat.com>
Subject: Re: [PATCH v2] Fix unwinding when restoring a register from one of a greater size
Date: Wed, 03 Sep 2025 14:08:50 +0100 [thread overview]
Message-ID: <87ecsnptst.fsf@redhat.com> (raw)
In-Reply-To: <20250810043552.1096935-1-kevinb@redhat.com>
Kevin Buettner <kevinb@redhat.com> writes:
> When debugging functions where a callee-saved register is moved to a
> register of a larger size (e.g., a 64-bit general-purpose register to
> a 128-bit vector register), GDB would crash when the user issued the
> "return" command. For example:
>
> ldgr %f0, %r11 ; Move 64-bit general-purpose register (r11)
> ; to 128-bit vector register (f0)
> .cfi_register r11, f0 ; DW_CFA_register: r11 is stored in f0
> ...
> lgdr %r11, %f0 ; Restore r11 from f0
> .cfi_restore r11 ; DW_CFA_restore: r11 is restored to its original
> ; register
>
> (This example uses instructions and registers for the S390x architecture,
> where this bug was originally found.)
>
> If GDB is stopped in the "..." section and the user issues the
> "return" command, GDB crashes due to a buffer size mismatch during
> unwinding. Specifically, in frame_register_unwind in frame.c, a
> buffer the size of the original register (the 64-bit r11 in this
> example) has been allocated and GDB would like to use memcpy to copy
> the contents of the register where the original register was saved
> (the 128-bit f0) to the buffer for the original register. But,
> fortunately, GDB has an assertion which prevents this from happening:
>
> gdb_assert (buffer.size () >= value->type ()->length ());
>
> This patch ensures that GDB uses the original register's type (e.g.,
> r11's type) when unwinding, even if it was marked as saved to a differently
> typed/sized register (e.g., f0) via .cfi_register (DW_CFA_register).
>
> The fix adds a 'struct type *' parameter to value_of_register_lazy() to
> explicitly track the original register's type. The function
> frame_unwind_got_register is updated to pass the correct type for the
> original register.
>
> The call chain from frame_register_unwind to frame_unwind_got_register
> is shown by this backtrace:
>
> #0 frame_unwind_got_register (frame=..., regnum=13, new_regnum=128)
> at gdb/frame-unwind.c:300
> #1 0x000000000135d894 in dwarf2_frame_prev_register (this_frame=...,
> this_cache=0x2204528, regnum=13)
> at gdb/dwarf2/frame.c:1187
> #2 0x00000000014d9186 in frame_unwind_legacy::prev_register (
> this=0x211f428 <dwarf2_frame_unwind>, this_frame=...,
> this_prologue_cache=0x2204528, regnum=13) at gdb/frame-unwind.c:401
> #3 0x00000000014e1d12 in frame_unwind_register_value (next_frame=...,
> regnum=13) at gdb/frame.c:1263
> #4 0x00000000014e16b8 in frame_register_unwind (next_frame=..., regnum=13,
> optimizedp=0x3ffffff813c, unavailablep=0x3ffffff8138,
> lvalp=0x3ffffff8134, addrp=0x3ffffff8128, realnump=0x3ffffff8124,
> buffer=...) at gdb/frame.c:1189
>
> The register numbers shown above are for s390x. On s390x,
> S390_R11_REGNUM has value 13. Vector registers (like f0) are numbered
> differently from floating-point registers of the same name, leading to
> regnum 128 for f0 despite S390_F0_REGNUM being assigned a different
> value in s390-tdep.h.
>
> New test cases for aarch64 and x86_64 check for this on more popular
> architectures and also without dependency on a particular compiler to
> generate an unusual prologue in which a general purpose register is
> being moved to a vector register. In both cases, the test simulates
> the bug found on s390x where a 64-bit frame pointer was being moved to
> a much wider vector register. These test cases will cause an internal
> error on their respective architecture, but will pass with this fix in
> place.
>
> When tested on s390x linux (native), this change fixes 59 GDB internal
> errors and around 200 failures overall. This is the list of internal
> errors that no longer occur on s390x:
>
> FAIL: gdb.base/call-sc.exp: tc: return foo; return call-sc-tc (GDB internal error)
> FAIL: gdb.base/call-sc.exp: td: return foo; return call-sc-td (GDB internal error)
> FAIL: gdb.base/call-sc.exp: te: return foo; return call-sc-te (GDB internal error)
> FAIL: gdb.base/call-sc.exp: tf: return foo; return call-sc-tf (GDB internal error)
> FAIL: gdb.base/call-sc.exp: ti: return foo; return call-sc-ti (GDB internal error)
> FAIL: gdb.base/call-sc.exp: tl: return foo; return call-sc-tl (GDB internal error)
> FAIL: gdb.base/call-sc.exp: tld: return foo; return call-sc-tld (GDB internal error)
> FAIL: gdb.base/call-sc.exp: tll: return foo; return call-sc-tll (GDB internal error)
> FAIL: gdb.base/call-sc.exp: ts: return foo; return call-sc-ts (GDB internal error)
> FAIL: gdb.base/gnu_vector.exp: return from vector-valued function (GDB internal error)
> FAIL: gdb.base/return-3.exp: in foo: return (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: double: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: float: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: int: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: long-long: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: long: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: short: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return-nodebug.exp: signed-char: return from function with no debug info with a cast (GDB internal error)
> FAIL: gdb.base/return.exp: return value 5 (GDB internal error)
> FAIL: gdb.base/return.exp: return value 5.0 (GDB internal error)
> FAIL: gdb.base/return2.exp: return from char_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from double_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from float_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from int_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from long_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from long_long_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from short_func (GDB internal error)
> FAIL: gdb.base/return2.exp: return from void_func (GDB internal error)
> FAIL: gdb.base/sigstep.exp: return from handleri: leave handler (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc-ti: return foo<n>; return 2 structs-tc-ti (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc-tl: return foo<n>; return 2 structs-tc-tl (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc-ts: return foo<n>; return 2 structs-tc-ts (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 1 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 2 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 3 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 4 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 5 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 6 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 7 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tc: return foo<n>; return 8 structs-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=td-tf: return foo<n>; return 2 structs-td-tf (GDB internal error)
> FAIL: gdb.base/structs.exp: types=td: return foo<n>; return 1 structs-td (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tf-tc: return foo<n>; return 2 structs-tf-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tf-td: return foo<n>; return 2 structs-tf-td (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tf: return foo<n>; return 1 structs-tf (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tf: return foo<n>; return 2 structs-tf (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ti-tc: return foo<n>; return 2 structs-ti-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ti: return foo<n>; return 1 structs-ti (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ti: return foo<n>; return 2 structs-ti (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tl-tc: return foo<n>; return 2 structs-tl-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tl: return foo<n>; return 1 structs-tl (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tl: return foo<n>; return 2 structs-tl (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tld: return foo<n>; return 1 structs-tld (GDB internal error)
> FAIL: gdb.base/structs.exp: types=tll: return foo<n>; return 1 structs-tll (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ts-tc: return foo<n>; return 2 structs-ts-tc (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ts: return foo<n>; return 1 structs-ts (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ts: return foo<n>; return 2 structs-ts (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ts: return foo<n>; return 3 structs-ts (GDB internal error)
> FAIL: gdb.base/structs.exp: types=ts: return foo<n>; return 4 structs-ts (GDB internal error)
>
> I have tested this commit on Fedora Linux, with architectures s390x,
> x86_64, x86_64/-m32, aarch64, ppc64le, and riscv64, with no
> regressions found.
>
> This v2 version makes some changes suggested by Andrew Burgess: It
> adds an assert to frame_unwind_got_register() and always passes the
> type of REGNUM to value_of_register_lazy(). It also updates value.h's
> comment describing value_of_register_lazy().
Thanks for the update Kevin. I think the GDB changes are fine now. I
have a few nits with the test cases, but they are pretty minor, I don't
think you need to post a new version.
> diff --git a/gdb/testsuite/gdb.arch/aarch64-frameptr-vecreg-unwind.c b/gdb/testsuite/gdb.arch/aarch64-frameptr-vecreg-unwind.c
> new file mode 100644
> index 00000000000..44ed3e8f293
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-frameptr-vecreg-unwind.c
> @@ -0,0 +1,62 @@
> +/* Copyright 2025 Free Software Foundation, Inc.
> +
> + This file is part of GDB.
> +
> + 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/>. */
> +
> +volatile void dummy () {}
> +
> +long test_function(void)
> +{
> + __asm__ volatile (
> + /* Zero d0 (64-bit vector register part of v0). */
> + "movi d0, #0\n\t"
> +
> + /* Move the frame pointer (x29) to d0 using fmov. */
> + "fmov d0, x29\n\t"
> +
> + /* Describe CFI: Frame pointer is now in d0. */
> + ".cfi_register x29, d0\n\t"
> +
> + /* Clobber list: Specify all modified registers. */
> + : /* No output operands. */
> + : /* No input operands. */
> + : "d0"
> + );
> +
> + dummy (); /* break-here */
> +
> + __asm__ volatile (
> + /* Restore the frame pointer (x29) from d0 using fmov. */
> + "fmov x29, d0\n\t"
> +
> + /* Describe CFI: Frame pointer is restored. */
> + ".cfi_restore x29\n\t"
> +
> + /* Clobber list: Specify all modified registers. */
> + : /* No output operands. */
> + : /* No input operands. */
> + : "x29", "d0"
> + );
> +
> + return 0;
> +}
> +
> +int
> +main ()
> +{
> + long result = test_function ();
> + dummy ();
> + return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-frameptr-vecreg-unwind.exp b/gdb/testsuite/gdb.arch/aarch64-frameptr-vecreg-unwind.exp
> new file mode 100644
> index 00000000000..32db95fa4fe
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-frameptr-vecreg-unwind.exp
> @@ -0,0 +1,45 @@
> +# 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/>.
> +
> +require is_aarch64_target
> +
> +standard_testfile .c
.c is the default, so this is usually dropped.
> +
> +if { [prepare_for_testing "failed to prepare" ${testfile} \
> + "${srcfile}" {debug}] } {
> + return -1
The '-1' return value here is meaningless, could you remove it please.
> +}
> +
> +clean_restart ${binfile}
prepare_for_testing already includes a call to clean_restart passing in
binfile, so this line should be removed.
> +
> +if {![runto_main]} {
> + return
> +}
> +
> +gdb_breakpoint [gdb_get_line_number "break-here"]
> +gdb_continue_to_breakpoint "break-here"
> +
> +gdb_test_multiple "return -1" "return value -1" {
> + -re "Make .* return now.*y or n. $" {
> + send_gdb "y\n"
> + exp_continue
> + }
> + -re ".*result = test_function.*$gdb_prompt $" {
> + # pass without comment
> + }
> +}
Optionally, you could simplify this test to:
gdb_test "with confirm off -- return -1" "result = test_function \\(\\);"
If you don't like the use of 'with', then you should consider using:
-re -wrap ".*result = test_function \\(\\);" {
...
}
But I do prefer the 'with' approach myself.
I believe that all these comments also apply to the x86 version of the
test too.
But with these fixed:
Approved-By: Andrew Burgess <aburgess@redhat.com>
Thanks,
Andrew
> +
> +gdb_test "step" "dummy \\(\\);"
> +gdb_test "print result" "= -1"
> diff --git a/gdb/testsuite/gdb.arch/amd64-frameptr-vecreg-unwind.c b/gdb/testsuite/gdb.arch/amd64-frameptr-vecreg-unwind.c
> new file mode 100644
> index 00000000000..1b79a650b9c
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/amd64-frameptr-vecreg-unwind.c
> @@ -0,0 +1,63 @@
> +/* Copyright 2025 Free Software Foundation, Inc.
> +
> + This file is part of GDB.
> +
> + 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/>. */
> +
> +volatile void dummy () {}
> +
> +long test_function(void)
> +{
> + __asm__ volatile (
> + /* Clear xmm0. */
> + "vxorps %%xmm0, %%xmm0, %%xmm0\n\t"
> +
> + /* Move the frame pointer (rbp) to xmm0. */
> + "movq %%rbp, %%xmm0\n\t"
> +
> + /* CFI: Frame pointer is in xmm0. */
> + ".cfi_register %%rbp, %%xmm0\n\t"
> +
> + /* Clobber list: Specify all modified registers */
> + : // No output operands
> + : // No input operands
> + : "xmm0"
> + );
> +
> + dummy (); /* break-here */
> +
> + /* Pseudo-epilogue: Restore rbp from xmm0. */
> + __asm__ volatile (
> + /* Restore rbp. */
> + "movq %%xmm0, %%rbp\n\t"
> +
> + /* Describe CFI: Frame pointer is restored. */
> + ".cfi_restore %%rbp\n\t"
> +
> + /* Clobber list: Specify all modified registers */
> + : /* No output operands. */
> + : /* No input operands. */
> + : /* Despite clobbering rbp, gcc doesn't let us list it here. */
> + );
> +
> + return 0;
> +}
> +
> +int
> +main ()
> +{
> + long result = test_function ();
> + dummy ();
> + return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/amd64-frameptr-vecreg-unwind.exp b/gdb/testsuite/gdb.arch/amd64-frameptr-vecreg-unwind.exp
> new file mode 100644
> index 00000000000..2489f90fc84
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/amd64-frameptr-vecreg-unwind.exp
> @@ -0,0 +1,52 @@
> +# 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 test verifies that we can read and write the value of a pseudo register
> +# in unwound frames. For the test, we choose one raw register, rbx, and one
> +# pseudo register that is backed by rbx, ebx. We have two frames (the inner one,
> +# #0 and the outer one, #1) that each set a value for rbx. We verify that we
> +# can read both rbx and ebx correctly for each frame, and that when we write to
> +# ebx, rbx for that frame is correctly updated.
> +
> +require is_x86_64_m64_target
> +
> +standard_testfile .c
> +
> +if { [prepare_for_testing "failed to prepare" ${testfile} \
> + "${srcfile}" {debug}] } {
> + return -1
> +}
> +
> +clean_restart ${binfile}
> +
> +if {![runto_main]} {
> + return
> +}
> +
> +gdb_breakpoint [gdb_get_line_number "break-here"]
> +gdb_continue_to_breakpoint "break-here"
> +
> +gdb_test_multiple "return -1" "return value -1" {
> + -re "Make .* return now.*y or n. $" {
> + send_gdb "y\n"
> + exp_continue
> + }
> + -re ".*result = test_function.*$gdb_prompt $" {
> + # pass without comment
> + }
> +}
> +
> +gdb_test "step" "dummy \\(\\);"
> +gdb_test "print result" "= -1"
> diff --git a/gdb/value.h b/gdb/value.h
> index 0c7c785bee5..374934d20f6 100644
> --- a/gdb/value.h
> +++ b/gdb/value.h
> @@ -1156,9 +1156,12 @@ extern struct value *address_of_variable (struct symbol *var,
>
> extern value *value_of_register (int regnum, const frame_info_ptr &next_frame);
>
> -/* Same as the above, but the value is not fetched. */
> +/* Same as the above, but the value is not fetched. If TYPE is
> + non-nullptr, use it as the value type. Otherwise, 'register_type'
> + will be used to obtain the type. */
>
> -extern value *value_of_register_lazy (const frame_info_ptr &next_frame, int regnum);
> +extern value *value_of_register_lazy (const frame_info_ptr &next_frame,
> + int regnum, struct type *type = nullptr);
>
> /* Return the symbol's reading requirement. */
>
> --
> 2.50.1
next prev parent reply other threads:[~2025-09-03 13:10 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-10 4:34 Kevin Buettner
2025-09-03 13:08 ` Andrew Burgess [this message]
2025-09-11 2:46 ` Kevin Buettner
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=87ecsnptst.fsf@redhat.com \
--to=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
--cc=kevinb@redhat.com \
/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