[gdb/testsuite] Add gdb.base/unwind-on-each-insn-{amd64, i386}.exp

Message ID 20230125200910.29700-1-tdevries@suse.de
State Committed
Headers
Series [gdb/testsuite] Add gdb.base/unwind-on-each-insn-{amd64, i386}.exp |

Commit Message

Tom de Vries Jan. 25, 2023, 8:09 p.m. UTC
  The gcc 4.4.x (and earlier) compilers had the problem that the unwind info in
the epilogue was inaccurate.

In order to work around this in gdb, epilogue unwinders were added with a
higher priority than the dwarf unwinders in the amd64 and i386 targets:
- amd64_epilogue_frame_unwind, and
- i386_epilogue_frame_unwind.

Subsequently, the epilogue unwind info problem got fixed in gcc 4.5.0.

However, the epilogue unwinders prevented gdb from taking advantage of the
fixed epilogue unwind info, so the scope of the epilogue unwinders was
limited, bailing out for gcc >= 4.5.0.

There was no regression test added for this preference scheme, so if we now
declare epilogue unwind info from all gcc versions as trusted, no test will
start failing.

Fix this by adding an amd64 and i386 regression test for this.

I have no gcc 4.4.x lying around, so I fabricated the assembly files by:
- commenting out some .cfi directives to break the epilogue unwind info, and
- hand-editing the producer info to 4.4.7 to activate the fix.

Tested on x86_64-linux, target boards unix/{-m64,-m32}.
---
 .../gdb.base/unwind-on-each-insn-amd64.exp    |  42 +++
 .../gdb.base/unwind-on-each-insn-amd64.s      | 263 ++++++++++++++++++
 .../gdb.base/unwind-on-each-insn-i386.exp     |  39 +++
 .../gdb.base/unwind-on-each-insn-i386.s       | 262 +++++++++++++++++
 .../gdb.base/unwind-on-each-insn.exp          | 160 +----------
 .../gdb.base/unwind-on-each-insn.exp.tcl      | 180 ++++++++++++
 6 files changed, 792 insertions(+), 154 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.exp
 create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.s
 create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-i386.exp
 create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-i386.s
 create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn.exp.tcl


base-commit: 6121eeb72978cc5749c4c9f119b4dbaf637517c9
  

Comments

Tom de Vries Jan. 26, 2023, 4:21 p.m. UTC | #1
On 1/25/23 21:09, Tom de Vries via Gdb-patches wrote:
> The gcc 4.4.x (and earlier) compilers had the problem that the unwind info in
> the epilogue was inaccurate.
> 
> In order to work around this in gdb, epilogue unwinders were added with a
> higher priority than the dwarf unwinders in the amd64 and i386 targets:
> - amd64_epilogue_frame_unwind, and
> - i386_epilogue_frame_unwind.
> 
> Subsequently, the epilogue unwind info problem got fixed in gcc 4.5.0.
> 
> However, the epilogue unwinders prevented gdb from taking advantage of the
> fixed epilogue unwind info, so the scope of the epilogue unwinders was
> limited, bailing out for gcc >= 4.5.0.
> 
> There was no regression test added for this preference scheme, so if we now
> declare epilogue unwind info from all gcc versions as trusted, no test will
> start failing.
> 
> Fix this by adding an amd64 and i386 regression test for this.
> 
> I have no gcc 4.4.x lying around, so I fabricated the assembly files by:
> - commenting out some .cfi directives to break the epilogue unwind info, and
> - hand-editing the producer info to 4.4.7 to activate the fix.
> 
> Tested on x86_64-linux, target boards unix/{-m64,-m32}.
> ---
>   .../gdb.base/unwind-on-each-insn-amd64.exp    |  42 +++
>   .../gdb.base/unwind-on-each-insn-amd64.s      | 263 ++++++++++++++++++
>   .../gdb.base/unwind-on-each-insn-i386.exp     |  39 +++
>   .../gdb.base/unwind-on-each-insn-i386.s       | 262 +++++++++++++++++
>   .../gdb.base/unwind-on-each-insn.exp          | 160 +----------
>   .../gdb.base/unwind-on-each-insn.exp.tcl      | 180 ++++++++++++
>   6 files changed, 792 insertions(+), 154 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.exp
>   create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.s
>   create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-i386.exp
>   create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn-i386.s
>   create mode 100644 gdb/testsuite/gdb.base/unwind-on-each-insn.exp.tcl

I've fixed some nits and pushed this.

I did wonder about eliminating commonality between 
unwind-on-each-insn-i386.exp and unwind-on-each-insn-amd64.exp, but 
decided it was not worth the trouble.

Thanks,
- Tom
  
Tom Tromey Jan. 26, 2023, 6:22 p.m. UTC | #2
>>>>> "Tom" == Tom de Vries via Gdb-patches <gdb-patches@sourceware.org> writes:

Tom> I did wonder about eliminating commonality between
Tom> unwind-on-each-insn-i386.exp and unwind-on-each-insn-amd64.exp, but
Tom> decided it was not worth the trouble.

One thing some gdb.arch tests do is test the arch and then set some
variables based on that.  So I guess if you wanted there could be a
single .exp file and not the .exp/.tcl split.

Tom
  
Tom de Vries Jan. 27, 2023, 8:50 p.m. UTC | #3
On 1/26/23 19:22, Tom Tromey wrote:
>>>>>> "Tom" == Tom de Vries via Gdb-patches <gdb-patches@sourceware.org> writes:
> 
> Tom> I did wonder about eliminating commonality between
> Tom> unwind-on-each-insn-i386.exp and unwind-on-each-insn-amd64.exp, but
> Tom> decided it was not worth the trouble.
> 
> One thing some gdb.arch tests do is test the arch and then set some
> variables based on that.  So I guess if you wanted there could be a
> single .exp file and not the .exp/.tcl split.

I could merge unwind-on-each-insn-i386.exp and 
unwind-on-each-insn-amd64.exp, but I still would require the split 
because also unwind-on-each-insn.exp use the .tcl file.

Anyway, I've been playing with the idea of enabling 
unwind-on-each-insn-i386.exp on x86_64, using -m32 (but that might be 
part of a larger exercise, extending to all gdb.arch i386 test-cases). 
And for that I'd need unwind-on-each-insn-i386.exp separate from 
unwind-on-each-insn-amd64.exp.

So, for now I'm keeping this as is.

Thanks,
- Tom
  

Patch

diff --git a/gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.exp b/gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.exp
new file mode 100644
index 00000000000..39b566c70db
--- /dev/null
+++ b/gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.exp
@@ -0,0 +1,42 @@ 
+# Copyright 2023 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/>.  */
+
+if { ![istarget x86_64-*-* ] || ![is_lp64_target] } {
+    unsupported "is not an amd64/m64 target"
+    return
+}
+
+set srcfile_flags {}
+set srcfile2_flags {}
+set srcfile_debug 1
+set srcfile2_debug 1
+
+if [info exists COMPILE] {
+    standard_testfile unwind-on-each-insn.c unwind-on-each-insn-foo.c
+    # When updating the .s file, use these flags to generate the file:
+    #lappend srcfile2_flags additional_flags=-save-temps
+    #lappend srcfile2_flags additional_flags=-dA
+    # and do the following:
+    # - copy it in place, run the test-case and verify that all test pass.
+    # - break the epilogue unwind info by commenting out the cfi directive
+    #   before ret, and verify that some tests start failing.
+    # - change the producer strings to 4.4.7 (for completeness, do this also
+    #   in the comments generated by -dA), and verify that we have all
+    #   passes again.
+} else {
+    standard_testfile unwind-on-each-insn.c .s
+}
+
+source $srcdir/$subdir/unwind-on-each-insn.exp.tcl
diff --git a/gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.s b/gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.s
new file mode 100644
index 00000000000..0def4f61457
--- /dev/null
+++ b/gdb/testsuite/gdb.base/unwind-on-each-insn-amd64.s
@@ -0,0 +1,263 @@ 
+	.file	"unwind-on-each-insn-foo.c"
+	.text
+.Ltext0:
+	.globl	foo
+	.type	foo, @function
+foo:
+.LFB0:
+	.file 1 "/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c"
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:20
+	.loc 1 20 0
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushq	%rbp
+	.cfi_def_cfa_offset 16
+	.cfi_offset 6, -16
+	movq	%rsp, %rbp
+	.cfi_def_cfa_register 6
+	movq	%rdi, -8(%rbp)
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:22
+	.loc 1 22 0
+	nop
+	popq	%rbp
+	#.cfi_def_cfa 7, 8
+# SUCC: EXIT [100.0%] 
+	ret
+	.cfi_endproc
+.LFE0:
+	.size	foo, .-foo
+	.globl	bar
+	.type	bar, @function
+bar:
+.LFB1:
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:26
+	.loc 1 26 0
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushq	%rbp
+	.cfi_def_cfa_offset 16
+	.cfi_offset 6, -16
+	movq	%rsp, %rbp
+	.cfi_def_cfa_register 6
+	subq	$8, %rsp
+	movq	%rdi, -8(%rbp)
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:27
+	.loc 1 27 0
+	movq	-8(%rbp), %rax
+	movq	%rax, %rdi
+	call	foo
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:28
+	.loc 1 28 0
+	nop
+	leave
+	#.cfi_def_cfa 7, 8
+# SUCC: EXIT [100.0%] 
+	ret
+	.cfi_endproc
+.LFE1:
+	.size	bar, .-bar
+.Letext0:
+	.section	.debug_info,"",@progbits
+.Ldebug_info0:
+	.long	0x8c	# Length of Compilation Unit Info
+	.value	0x4	# DWARF version number
+	.long	.Ldebug_abbrev0	# Offset Into Abbrev. Section
+	.byte	0x8	# Pointer Size (in bytes)
+	.uleb128 0x1	# (DIE (0xb) DW_TAG_compile_unit)
+	.long	.LASF0	# DW_AT_producer: "GNU C11 4.4.7 -mtune=generic -march=x86-64 -g -fno-stack-protector"
+	.byte	0xc	# DW_AT_language
+	.long	.LASF1	# DW_AT_name: "/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c"
+	.long	.LASF2	# DW_AT_comp_dir: "/home/vries/gdb_versions/devel/build/gdb/testsuite"
+	.quad	.Ltext0	# DW_AT_low_pc
+	.quad	.Letext0-.Ltext0	# DW_AT_high_pc
+	.long	.Ldebug_line0	# DW_AT_stmt_list
+	.uleb128 0x2	# (DIE (0x2d) DW_TAG_subprogram)
+			# DW_AT_external
+	.ascii "bar\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x19	# DW_AT_decl_line
+			# DW_AT_prototyped
+	.quad	.LFB1	# DW_AT_low_pc
+	.quad	.LFE1-.LFB1	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_tail_call_sites
+	.long	0x57	# DW_AT_sibling
+	.uleb128 0x3	# (DIE (0x4a) DW_TAG_formal_parameter)
+	.ascii "s\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x19	# DW_AT_decl_line
+	.long	0x57	# DW_AT_type
+	.uleb128 0x2	# DW_AT_location
+	.byte	0x91	# DW_OP_fbreg
+	.sleb128 -24
+	.byte	0	# end of children of DIE 0x2d
+	.uleb128 0x4	# (DIE (0x57) DW_TAG_pointer_type)
+	.byte	0x8	# DW_AT_byte_size
+	.long	0x64	# DW_AT_type
+	.uleb128 0x5	# (DIE (0x5d) DW_TAG_base_type)
+	.byte	0x1	# DW_AT_byte_size
+	.byte	0x6	# DW_AT_encoding
+	.long	.LASF3	# DW_AT_name: "char"
+	.uleb128 0x6	# (DIE (0x64) DW_TAG_const_type)
+	.long	0x5d	# DW_AT_type
+	.uleb128 0x7	# (DIE (0x69) DW_TAG_subprogram)
+			# DW_AT_external
+	.ascii "foo\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x13	# DW_AT_decl_line
+			# DW_AT_prototyped
+	.quad	.LFB0	# DW_AT_low_pc
+	.quad	.LFE0-.LFB0	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_call_sites
+	.uleb128 0x3	# (DIE (0x82) DW_TAG_formal_parameter)
+	.ascii "s\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x13	# DW_AT_decl_line
+	.long	0x57	# DW_AT_type
+	.uleb128 0x2	# DW_AT_location
+	.byte	0x91	# DW_OP_fbreg
+	.sleb128 -24
+	.byte	0	# end of children of DIE 0x69
+	.byte	0	# end of children of DIE 0xb
+	.section	.debug_abbrev,"",@progbits
+.Ldebug_abbrev0:
+	.uleb128 0x1	# (abbrev code)
+	.uleb128 0x11	# (TAG: DW_TAG_compile_unit)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x25	# (DW_AT_producer)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x13	# (DW_AT_language)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x1b	# (DW_AT_comp_dir)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x10	# (DW_AT_stmt_list)
+	.uleb128 0x17	# (DW_FORM_sec_offset)
+	.byte	0
+	.byte	0
+	.uleb128 0x2	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2116	# (DW_AT_GNU_all_tail_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x1	# (DW_AT_sibling)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x3	# (abbrev code)
+	.uleb128 0x5	# (TAG: DW_TAG_formal_parameter)
+	.byte	0	# DW_children_no
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.uleb128 0x2	# (DW_AT_location)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.byte	0
+	.byte	0
+	.uleb128 0x4	# (abbrev code)
+	.uleb128 0xf	# (TAG: DW_TAG_pointer_type)
+	.byte	0	# DW_children_no
+	.uleb128 0xb	# (DW_AT_byte_size)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x5	# (abbrev code)
+	.uleb128 0x24	# (TAG: DW_TAG_base_type)
+	.byte	0	# DW_children_no
+	.uleb128 0xb	# (DW_AT_byte_size)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3e	# (DW_AT_encoding)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.byte	0
+	.byte	0
+	.uleb128 0x6	# (abbrev code)
+	.uleb128 0x26	# (TAG: DW_TAG_const_type)
+	.byte	0	# DW_children_no
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x7	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2117	# (DW_AT_GNU_all_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.byte	0
+	.byte	0
+	.byte	0
+	.section	.debug_aranges,"",@progbits
+	.long	0x2c	# Length of Address Ranges Info
+	.value	0x2	# DWARF Version
+	.long	.Ldebug_info0	# Offset of Compilation Unit Info
+	.byte	0x8	# Size of Address
+	.byte	0	# Size of Segment Descriptor
+	.value	0	# Pad to 16 byte boundary
+	.value	0
+	.quad	.Ltext0	# Address
+	.quad	.Letext0-.Ltext0	# Length
+	.quad	0
+	.quad	0
+	.section	.debug_line,"",@progbits
+.Ldebug_line0:
+	.section	.debug_str,"MS",@progbits,1
+.LASF0:
+	.string	"GNU C11 4.4.7 -mtune=generic -march=x86-64 -g -fno-stack-protector"
+.LASF1:
+	.string	"/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c"
+.LASF2:
+	.string	"/home/vries/gdb_versions/devel/build/gdb/testsuite"
+.LASF3:
+	.string	"char"
+	.ident	"GCC: (SUSE Linux) 7.5.0"
+	.section	.note.GNU-stack,"",@progbits
diff --git a/gdb/testsuite/gdb.base/unwind-on-each-insn-i386.exp b/gdb/testsuite/gdb.base/unwind-on-each-insn-i386.exp
new file mode 100644
index 00000000000..457ffa3d1cd
--- /dev/null
+++ b/gdb/testsuite/gdb.base/unwind-on-each-insn-i386.exp
@@ -0,0 +1,39 @@ 
+# Copyright 2023 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_x86_like_target
+
+set srcfile_flags {}
+set srcfile2_flags {}
+set srcfile_debug 1
+set srcfile2_debug 1
+
+if [info exists COMPILE] {
+    standard_testfile unwind-on-each-insn.c unwind-on-each-insn-foo.c
+    # When updating the .s file, use these flags to generate the file:
+    #lappend srcfile2_flags additional_flags=-save-temps
+    #lappend srcfile2_flags additional_flags=-dA
+    # and do the following:
+    # - copy it in place, run the test-case and verify that all test pass.
+    # - break the epilogue unwind info by commenting out the cfi directive
+    #   before ret, and verify that some tests start failing.
+    # - change the producer strings to 4.4.7 (for completeness, do this also
+    #   in the comments generated by -dA), and verify that we have all
+    #   passes again.
+} else {
+    standard_testfile unwind-on-each-insn.c .s
+}
+
+source $srcdir/$subdir/unwind-on-each-insn.exp.tcl
diff --git a/gdb/testsuite/gdb.base/unwind-on-each-insn-i386.s b/gdb/testsuite/gdb.base/unwind-on-each-insn-i386.s
new file mode 100644
index 00000000000..d0f96bef252
--- /dev/null
+++ b/gdb/testsuite/gdb.base/unwind-on-each-insn-i386.s
@@ -0,0 +1,262 @@ 
+	.file	"unwind-on-each-insn-foo.c"
+	.text
+.Ltext0:
+	.globl	foo
+	.type	foo, @function
+foo:
+.LFB0:
+	.file 1 "/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c"
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:20
+	.loc 1 20 0
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushl	%ebp
+	.cfi_def_cfa_offset 8
+	.cfi_offset 5, -8
+	movl	%esp, %ebp
+	.cfi_def_cfa_register 5
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:22
+	.loc 1 22 0
+	nop
+	popl	%ebp
+	.cfi_restore 5
+#	.cfi_def_cfa 4, 4
+# SUCC: EXIT [100.0%] 
+	ret
+	.cfi_endproc
+.LFE0:
+	.size	foo, .-foo
+	.globl	bar
+	.type	bar, @function
+bar:
+.LFB1:
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:26
+	.loc 1 26 0
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushl	%ebp
+	.cfi_def_cfa_offset 8
+	.cfi_offset 5, -8
+	movl	%esp, %ebp
+	.cfi_def_cfa_register 5
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:27
+	.loc 1 27 0
+	pushl	8(%ebp)
+	call	foo
+	addl	$4, %esp
+	# /home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c:28
+	.loc 1 28 0
+	nop
+	leave
+	.cfi_restore 5
+#	.cfi_def_cfa 4, 4
+# SUCC: EXIT [100.0%] 
+	ret
+	.cfi_endproc
+.LFE1:
+	.size	bar, .-bar
+.Letext0:
+	.section	.debug_info,"",@progbits
+.Ldebug_info0:
+	.long	0x74	# Length of Compilation Unit Info
+	.value	0x4	# DWARF version number
+	.long	.Ldebug_abbrev0	# Offset Into Abbrev. Section
+	.byte	0x4	# Pointer Size (in bytes)
+	.uleb128 0x1	# (DIE (0xb) DW_TAG_compile_unit)
+	.long	.LASF0	# DW_AT_producer: "GNU C11 4.4.7 -m32 -mtune=generic -march=x86-64 -g -fno-stack-protector"
+	.byte	0xc	# DW_AT_language
+	.long	.LASF1	# DW_AT_name: "/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c"
+	.long	.LASF2	# DW_AT_comp_dir: "/home/vries/gdb_versions/devel/build/gdb/testsuite"
+	.long	.Ltext0	# DW_AT_low_pc
+	.long	.Letext0-.Ltext0	# DW_AT_high_pc
+	.long	.Ldebug_line0	# DW_AT_stmt_list
+	.uleb128 0x2	# (DIE (0x25) DW_TAG_subprogram)
+			# DW_AT_external
+	.ascii "bar\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x19	# DW_AT_decl_line
+			# DW_AT_prototyped
+	.long	.LFB1	# DW_AT_low_pc
+	.long	.LFE1-.LFB1	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_tail_call_sites
+	.long	0x47	# DW_AT_sibling
+	.uleb128 0x3	# (DIE (0x3a) DW_TAG_formal_parameter)
+	.ascii "s\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x19	# DW_AT_decl_line
+	.long	0x47	# DW_AT_type
+	.uleb128 0x2	# DW_AT_location
+	.byte	0x91	# DW_OP_fbreg
+	.sleb128 0
+	.byte	0	# end of children of DIE 0x25
+	.uleb128 0x4	# (DIE (0x47) DW_TAG_pointer_type)
+	.byte	0x4	# DW_AT_byte_size
+	.long	0x54	# DW_AT_type
+	.uleb128 0x5	# (DIE (0x4d) DW_TAG_base_type)
+	.byte	0x1	# DW_AT_byte_size
+	.byte	0x6	# DW_AT_encoding
+	.long	.LASF3	# DW_AT_name: "char"
+	.uleb128 0x6	# (DIE (0x54) DW_TAG_const_type)
+	.long	0x4d	# DW_AT_type
+	.uleb128 0x7	# (DIE (0x59) DW_TAG_subprogram)
+			# DW_AT_external
+	.ascii "foo\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x13	# DW_AT_decl_line
+			# DW_AT_prototyped
+	.long	.LFB0	# DW_AT_low_pc
+	.long	.LFE0-.LFB0	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_call_sites
+	.uleb128 0x3	# (DIE (0x6a) DW_TAG_formal_parameter)
+	.ascii "s\0"	# DW_AT_name
+	.byte	0x1	# DW_AT_decl_file (/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c)
+	.byte	0x13	# DW_AT_decl_line
+	.long	0x47	# DW_AT_type
+	.uleb128 0x2	# DW_AT_location
+	.byte	0x91	# DW_OP_fbreg
+	.sleb128 0
+	.byte	0	# end of children of DIE 0x59
+	.byte	0	# end of children of DIE 0xb
+	.section	.debug_abbrev,"",@progbits
+.Ldebug_abbrev0:
+	.uleb128 0x1	# (abbrev code)
+	.uleb128 0x11	# (TAG: DW_TAG_compile_unit)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x25	# (DW_AT_producer)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x13	# (DW_AT_language)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x1b	# (DW_AT_comp_dir)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x6	# (DW_FORM_data4)
+	.uleb128 0x10	# (DW_AT_stmt_list)
+	.uleb128 0x17	# (DW_FORM_sec_offset)
+	.byte	0
+	.byte	0
+	.uleb128 0x2	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x6	# (DW_FORM_data4)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2116	# (DW_AT_GNU_all_tail_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x1	# (DW_AT_sibling)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x3	# (abbrev code)
+	.uleb128 0x5	# (TAG: DW_TAG_formal_parameter)
+	.byte	0	# DW_children_no
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.uleb128 0x2	# (DW_AT_location)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.byte	0
+	.byte	0
+	.uleb128 0x4	# (abbrev code)
+	.uleb128 0xf	# (TAG: DW_TAG_pointer_type)
+	.byte	0	# DW_children_no
+	.uleb128 0xb	# (DW_AT_byte_size)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x5	# (abbrev code)
+	.uleb128 0x24	# (TAG: DW_TAG_base_type)
+	.byte	0	# DW_children_no
+	.uleb128 0xb	# (DW_AT_byte_size)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3e	# (DW_AT_encoding)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.byte	0
+	.byte	0
+	.uleb128 0x6	# (abbrev code)
+	.uleb128 0x26	# (TAG: DW_TAG_const_type)
+	.byte	0	# DW_children_no
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x7	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x6	# (DW_FORM_data4)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2117	# (DW_AT_GNU_all_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.byte	0
+	.byte	0
+	.byte	0
+	.section	.debug_aranges,"",@progbits
+	.long	0x1c	# Length of Address Ranges Info
+	.value	0x2	# DWARF Version
+	.long	.Ldebug_info0	# Offset of Compilation Unit Info
+	.byte	0x4	# Size of Address
+	.byte	0	# Size of Segment Descriptor
+	.value	0	# Pad to 8 byte boundary
+	.value	0
+	.long	.Ltext0	# Address
+	.long	.Letext0-.Ltext0	# Length
+	.long	0
+	.long	0
+	.section	.debug_line,"",@progbits
+.Ldebug_line0:
+	.section	.debug_str,"MS",@progbits,1
+.LASF0:
+	.string	"GNU C11 4.4.7 -m32 -mtune=generic -march=x86-64 -g -fno-stack-protector"
+.LASF2:
+	.string	"/home/vries/gdb_versions/devel/build/gdb/testsuite"
+.LASF1:
+	.string	"/home/vries/gdb_versions/devel/src/gdb/testsuite/gdb.base/unwind-on-each-insn-foo.c"
+.LASF3:
+	.string	"char"
+	.ident	"GCC: (SUSE Linux) 7.5.0"
+	.section	.note.GNU-stack,"",@progbits
diff --git a/gdb/testsuite/gdb.base/unwind-on-each-insn.exp b/gdb/testsuite/gdb.base/unwind-on-each-insn.exp
index 0d0683659c3..a4fbf4fd998 100644
--- a/gdb/testsuite/gdb.base/unwind-on-each-insn.exp
+++ b/gdb/testsuite/gdb.base/unwind-on-each-insn.exp
@@ -1,4 +1,4 @@ 
-# Copyright 2022-2023 Free Software Foundation, Inc.
+# Copyright 2023 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
@@ -13,159 +13,11 @@ 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-# Single step through a simple (empty) function that was compiled
-# without DWARF debug information.
-#
-# At each instruction check that the frame-id, and frame base address,
-# are calculated correctly.
-#
-# Additionally, check we can correctly unwind to the previous frame,
-# and that the previous stack-pointer value, and frame base address
-# value, can be calculated correctly.
-
 standard_testfile .c -foo.c
 
-set debug_flags {debug}
-set nodebug_flags {nodebug}
-
-# Make sure that we don't use .eh_frame info, by not generating it,
-# using -fno-asynchronous-unwind-tables, if supported.
-if { [gdb_can_simple_compile fno-asynchronous-unwind-tables \
-	  { void foo () { } } object -fno-asynchronous-unwind-tables] } {
-    lappend nodebug_flags additional_flags=-fno-asynchronous-unwind-tables
-}
-
-if {[prepare_for_testing_full "failed to prepare" \
-	 [list ${testfile} $debug_flags \
-	      $srcfile $debug_flags $srcfile2 $nodebug_flags]]} {
-    return -1
-}
-
-if {![runto_main]} {
-    return 0
-}
-
-# Return a two element list, the first element is the stack-pointer
-# value (from the $sp register), and the second element is the frame
-# base address (from the 'info frame' output).
-proc get_sp_and_fba { testname } {
-    with_test_prefix "get \$sp and frame base $testname" {
-	set sp [get_hexadecimal_valueof "\$sp" "*UNKNOWN*"]
-
-	set fba ""
-	gdb_test_multiple "info frame" "" {
-	    -re -wrap ".*Stack level ${::decimal}, frame at ($::hex):.*" {
-		set fba $expect_out(1,string)
-	    }
-	}
-
-	return [list $sp $fba]
-    }
-}
-
-# Return the frame-id of the current frame, collected using the 'maint
-# print frame-id' command.
-proc get_fid { } {
-    set fid ""
-    gdb_test_multiple "maint print frame-id" "" {
-	-re -wrap ".*frame-id for frame #${::decimal}: (.*)" {
-	    set fid $expect_out(1,string)
-	}
-    }
-    return $fid
-}
-
-# Record the current stack-pointer, and the frame base address.
-lassign [get_sp_and_fba "in main"] main_sp main_fba
-set main_fid [get_fid]
-
-proc do_test { function step_cmd } {
-    # Now enter the function.  Ideally, stop at the first insn, so set a
-    # breakpoint at "*$function".  The "*$function" breakpoint may not trigger
-    # for archs with gdbarch_skip_entrypoint_p, so set a backup breakpoint at
-    # "$function".
-    gdb_breakpoint "*$function"
-    gdb_breakpoint "$function"
-    gdb_continue_to_breakpoint "enter $function"
-    # Cleanup breakpoints.
-    delete_breakpoints
-
-    # Record the current stack-pointer, and the frame base address.
-    lassign [get_sp_and_fba "in $function"] fn_sp fn_fba
-    set fn_fid [get_fid]
-
-    for { set i_count 1 } { true } { incr i_count } {
-	with_test_prefix "instruction ${i_count}" {
-
-	    # The current stack-pointer value can legitimately change
-	    # throughout the lifetime of a function, so we don't check the
-	    # current stack-pointer value.  But the frame base address
-	    # should not change, so we do check for that.
-	    lassign [get_sp_and_fba "for fn"] sp_value fba_value
-	    gdb_assert { $fba_value == $fn_fba }
-
-	    # The frame-id should never change within a function, so check
-	    # that now.
-	    set fid [get_fid]
-	    gdb_assert { [string equal $fid $fn_fid] } \
-		"check frame-id matches"
-
-	    # Check that the previous frame is 'main'.
-	    gdb_test "bt 2" "\r\n#1\\s+\[^\r\n\]+ in main \\(\\)( .*)?"
-
-	    # Move up the stack (to main).
-	    gdb_test "up" \
-		"\r\n#1\\s+\[^\r\n\]+ in main \\(\\)( .*)?"
-
-	    # Check we can unwind the stack-pointer and the frame base
-	    # address correctly.
-	    lassign [get_sp_and_fba "for main"] sp_value fba_value
-	    if { $i_count == 1 } {
-		# The stack-pointer may have changed while running to *$function.
-		set ::main_sp $sp_value
-	    } else {
-		gdb_assert { $sp_value == $::main_sp }
-	    }
-	    gdb_assert { $fba_value == $::main_fba }
-
-	    # Check we have a consistent value for main's frame-id.
-	    set fid [get_fid]
-	    gdb_assert { [string equal $fid $::main_fid] }
-
-	    # Move back to the inner most frame.
-	    gdb_test "frame 0" ".*"
-
-	    if { $i_count > 100 } {
-		# We expect a handful of instructions, if we reach 100,
-		# something is going wrong.  Avoid an infinite loop.
-		fail "exceeded max number of instructions"
-		break
-	    }
-
-	    gdb_test $step_cmd
-
-	    set in_fn 0
-	    gdb_test_multiple "info frame" "" {
-		-re -wrap " = $::hex in ${function}( \\(.*\\))?;.*" {
-		    set in_fn 1
-		}
-		-re -wrap "" {}
-	    }
-
-	    if { ! $in_fn } {
-		break
-	    }
-	}
-    }
-}
+set srcfile_flags {}
+set srcfile2_flags {}
+set srcfile_debug 1
+set srcfile2_debug 0
 
-foreach {
-    function step_cmd
-} {
-    foo stepi
-    bar nexti
-} {
-    with_test_prefix $function {
-	do_test $function $step_cmd
-    }
-}
+source $srcdir/$subdir/unwind-on-each-insn.exp.tcl
diff --git a/gdb/testsuite/gdb.base/unwind-on-each-insn.exp.tcl b/gdb/testsuite/gdb.base/unwind-on-each-insn.exp.tcl
new file mode 100644
index 00000000000..45ed91a3986
--- /dev/null
+++ b/gdb/testsuite/gdb.base/unwind-on-each-insn.exp.tcl
@@ -0,0 +1,180 @@ 
+# Copyright 2022-2023 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/>.  */
+
+# Single step through a simple (empty) function that was compiled
+# without DWARF debug information.
+#
+# At each instruction check that the frame-id, and frame base address,
+# are calculated correctly.
+#
+# Additionally, check we can correctly unwind to the previous frame,
+# and that the previous stack-pointer value, and frame base address
+# value, can be calculated correctly.
+
+set debug_flags {debug}
+set nodebug_flags {nodebug}
+
+# Make sure that we don't use .eh_frame info, by not generating it,
+# using -fno-asynchronous-unwind-tables, if supported.
+if { [gdb_can_simple_compile fno-asynchronous-unwind-tables \
+	  { void foo () { } } object -fno-asynchronous-unwind-tables] } {
+    lappend nodebug_flags additional_flags=-fno-asynchronous-unwind-tables
+}
+
+if { $srcfile_debug } {
+    lappend srcfile_flags $debug_flags
+} else {
+    lappend srcfile_flags $nodebug_flags
+}
+if { $srcfile2_debug } {
+    lappend srcfile2_flags $debug_flags
+} else {
+    lappend srcfile2_flags $nodebug_flags
+}
+
+if {[prepare_for_testing_full "failed to prepare" \
+	 [list ${testfile} $debug_flags \
+	      $srcfile $srcfile_flags $srcfile2 $srcfile2_flags]]} {
+    return -1
+}
+
+if {![runto_main]} {
+    return 0
+}
+
+# Return a two element list, the first element is the stack-pointer
+# value (from the $sp register), and the second element is the frame
+# base address (from the 'info frame' output).
+proc get_sp_and_fba { testname } {
+    with_test_prefix "get \$sp and frame base $testname" {
+	set sp [get_hexadecimal_valueof "\$sp" "*UNKNOWN*"]
+
+	set fba ""
+	gdb_test_multiple "info frame" "" {
+	    -re -wrap ".*Stack level ${::decimal}, frame at ($::hex):.*" {
+		set fba $expect_out(1,string)
+	    }
+	}
+
+	return [list $sp $fba]
+    }
+}
+
+# Return the frame-id of the current frame, collected using the 'maint
+# print frame-id' command.
+proc get_fid { } {
+    set fid ""
+    gdb_test_multiple "maint print frame-id" "" {
+	-re -wrap ".*frame-id for frame #${::decimal}: (.*)" {
+	    set fid $expect_out(1,string)
+	}
+    }
+    return $fid
+}
+
+# Record the current stack-pointer, and the frame base address.
+lassign [get_sp_and_fba "in main"] main_sp main_fba
+set main_fid [get_fid]
+
+proc do_test { function step_cmd } {
+    # Now enter the function.  Ideally, stop at the first insn, so set a
+    # breakpoint at "*$function".  The "*$function" breakpoint may not trigger
+    # for archs with gdbarch_skip_entrypoint_p, so set a backup breakpoint at
+    # "$function".
+    gdb_breakpoint "*$function"
+    gdb_breakpoint "$function"
+    gdb_continue_to_breakpoint "enter $function"
+    # Cleanup breakpoints.
+    delete_breakpoints
+
+    # Record the current stack-pointer, and the frame base address.
+    lassign [get_sp_and_fba "in $function"] fn_sp fn_fba
+    set fn_fid [get_fid]
+
+    for { set i_count 1 } { true } { incr i_count } {
+	with_test_prefix "instruction ${i_count}" {
+
+	    # The current stack-pointer value can legitimately change
+	    # throughout the lifetime of a function, so we don't check the
+	    # current stack-pointer value.  But the frame base address
+	    # should not change, so we do check for that.
+	    lassign [get_sp_and_fba "for fn"] sp_value fba_value
+	    gdb_assert { $fba_value == $fn_fba }
+
+	    # The frame-id should never change within a function, so check
+	    # that now.
+	    set fid [get_fid]
+	    gdb_assert { [string equal $fid $fn_fid] } \
+		"check frame-id matches"
+
+	    # Check that the previous frame is 'main'.
+	    gdb_test "bt 2" "\r\n#1\\s+\[^\r\n\]+ in main \\(\\)( .*)?"
+
+	    # Move up the stack (to main).
+	    gdb_test "up" \
+		"\r\n#1\\s+\[^\r\n\]+ in main \\(\\)( .*)?"
+
+	    # Check we can unwind the stack-pointer and the frame base
+	    # address correctly.
+	    lassign [get_sp_and_fba "for main"] sp_value fba_value
+	    if { $i_count == 1 } {
+		# The stack-pointer may have changed while running to *$function.
+		set ::main_sp $sp_value
+	    } else {
+		gdb_assert { $sp_value == $::main_sp }
+	    }
+	    gdb_assert { $fba_value == $::main_fba }
+
+	    # Check we have a consistent value for main's frame-id.
+	    set fid [get_fid]
+	    gdb_assert { [string equal $fid $::main_fid] }
+
+	    # Move back to the inner most frame.
+	    gdb_test "frame 0" ".*"
+
+	    if { $i_count > 100 } {
+		# We expect a handful of instructions, if we reach 100,
+		# something is going wrong.  Avoid an infinite loop.
+		fail "exceeded max number of instructions"
+		break
+	    }
+
+	    gdb_test $step_cmd
+
+	    set in_fn 0
+	    gdb_test_multiple "info frame" "" {
+		-re -wrap " = $::hex in ${function}( \\(.*\\))?;.*" {
+		    set in_fn 1
+		}
+		-re -wrap "" {}
+	    }
+
+	    if { ! $in_fn } {
+		break
+	    }
+	}
+    }
+}
+
+foreach {
+    function step_cmd
+} {
+    foo stepi
+    bar nexti
+} {
+    with_test_prefix $function {
+	do_test $function $step_cmd
+    }
+}